Update Redux Store - react-redux

I am trying to update the redux store but when I try to access both points and sessionId, they come back undefined. I am sure there is a problem with my reducer, but I can't figure it out. Any help would be much appreciated.
Here's my reducer:
import { UPDATE_POINTS, SET_SESSION } from '../path'
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action) => {
switch (action.type) {
case UPDATE_POINTS:
return {
points: action.points
}
case SET_SESSION:
return {
sessionId: action.session
}
default:
return state;
}
}
Edit:
Action Creators
export function updatePoints(points){
return {
type: UPDATE_POINTS,
points
}
}
export function setSession(session){
return {
type: SET_SESSION,
session
}
}
Within React Component (for simplicity I took most everything else out of this function)
handleSelect(e) {
this.props.setSession(e);
console.log(this.props.sessionId);
}
This function is used when a menu item is chosen from a drop down menu. On the first selection, the console shows whatever is in the initial state for sessionId. Any further drop down selections result in undefined in the console.

You're super close. A reducer in redux needs to return the a new copy of the entire state. Your reducer is returning only the key it's concerned with, which is going to drop the other key. You need to return a new copy of the state with your key updated. For example:
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action = null) => {
// Exit early if you don't have an action (returning old state)
if (!action) return state;
// This function will assign your patch onto the old state, and then
// assign all of that onto a NEW object. For redux to do it's job,
// you can't modulate the old object, you have to return a new one.
const update = patch => Object.assign({}, state, patch);
switch (action.type) {
case UPDATE_POINTS:
return update({
points: action.points
});
case SET_SESSION:
return update({
sessionId: action.session
});
default:
return state;
}
}
And for the record, instead of putting your data payload under a unique key each time in your action creators, if you put the payload under a data key then your action will follow the standard flux action format.
export const updatePoints = (points) => ({
type: UPDATE_POINTS,
data: points
});
export const setSession = (session) => ({
type: SET_SESSION,
data: session
});
There you go. Good luck, and if you get stuck, refer back to the Redux docs (they're really good). Link to Redux Docs

Related

Why I am getting the key name as my reducer name when using useSelector hook

My Reducer
export default function saveContactsReducer(state = {}, { type, contacts }) {
switch (type) {
case types.SAVE_CONTACTS:
return {
...state,
contactsDetails: contacts,
currentUserName: contacts.personName
}
default:
return state;
}
}
My Action
import * as types from './actionTypes';
function saveContacts(contacts) {
return { type: types.SAVE_CONTACTS, contacts: contacts }
}
export default saveContacts;
My Action dispatch
dispatch(contactsAction(contactsDetails));
When I am using the useSelector hook from the react-redux
My state is like this
state.saveContactsReducer: {contacts}}
Instead I want state.contacts and don't want reducer name
Actually, the problem was the root reducer where I had passed all the reducers with their name. which I changed to the key which I wanted something like this.
Earliter it was like this
const rootReducer = combineReducers({
contactsReducer,
});
To fix changed the reducer name to the key which I wanted.
const rootReducer = combineReducers({
contacts,
});

React component not receiving intermediate state when chaining actions in redux-saga

I have two actions TEST and TEST_DONE which both increment an id property in my redux state. I am using redux-saga to dispatch the second action TEST_DONE automatically whenever I dispatch the first action TEST from my component.
I expect the order of execution to go like this:
component renders with initial value of testState.id = 0
component dispatches TEST action
component re-renders with testState.id = 1
saga dispatches the TEST_DONE action
component re-renders with testState.id = 2
Instead my component only re-renders when testState.id is updated to 2. I can't see the 1 value in the getSnapshotBeforeUpdate function. It shows 0 as the previous prop.
Why does the prop jump from 0 to 2 without receiving 1 in between?
saga.js:
export function* TestSagaFunc() {
yield put({
type: actions.TEST_DONE
});
};
export default function* rootSaga() {
yield all([
yield takeEvery(actions.TEST, TestSagaFunc),
]);
};
action.js:
const actions = {
TEST: 'TEST',
TEST_DONE: 'TEST_DONE',
callTest: (id) => ({
type: actions.TEST,
payload: {
id
}
}),
};
export default actions;
reducer.js:
const initState = {
testState: {
id: 0
}
};
export default function TestReducers ( state=initState, { type, ...action}) {
switch(type) {
default:
return state;
case actions.TEST: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
};
case actions.TEST_DONE: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
}
};
};
console output from component getSnapshotBeforeUpdate
Summarizing my comments from the question:
The redux state is indeed being updated as you've seen, but a component is not guaranteed to render every intermediate state change based on the way react batches state changes. To test this you can try importing delay from redux-saga/effects and adding yield delay(1000); before calling yield put in TestSagaFunc so the two state updates don't get batched together.
This is just a trick to illustrate the effects of batching and almost certainly not what you want to do. If you need the intermediate state to be rendered you could dispatch TEST_DONE from the component being rendered with a useEffect (or componentDidUpdate) to ensure that the component went through one render cycle with the intermediate state. But there is no way to force your component to render intermediate reducer states that are batched together.

Use an object property as a value with a different name in GraphQl

Internally in my server my entities are handled using the database's native fields where possible, so the entity's type is keyed with "dgraph.type". My graphql api does not need to know that the database is dgraph, but I don't want to have to change the field name on every resolver. Is it possible to create a Scalar or some other process so that I can send
{
"dgraph.type": "User",
uid: "0x01",
username: "JimNaysium",
}
and have the client receive
{
type: "User",
uid: "0x01",
username: "JimNaysium",
}
If you've found your way to this benighted question, the answer is: There is no built in way to do this. Apollo server's plugins all trigger too early or too late to help and the schema cannot rename properties. I solved this using the following code:
type KeyedList<T = any> = {[key: string]: any};
const getConvertTypesResolver = (
resolver: (parent: any, args: any, context: any, info: any) => Promise<any>
): any => {
return async (parent: any, args: any, context: any, info: any): Promise<any> => {
const result = await resolver(parent, args, context, info);
// Check for serializable data now so the processor does not choke on
// circular references later.
try {
JSON.stringify(result);
} catch (e) {
throw new Error("Resolver results must be serializable.");
}
const nestedKeysArrays: string[][] = [Object.keys(result)];
const path: KeyedList[] = [result];
// Iterate over every nested object in the result body.
while (nestedKeysArrays.length) {
let done = true;
// Iterate over every key of every object.
while (nestedKeysArrays[0].length) {
// Progressively destroy the keys arrays to prevent rework.
const key = nestedKeysArrays[0].shift();
if (!key) {
continue;
}
const current = path[0][key];
// If the current key is an object add it to the beginning of the
// lists and begin processing it now.
if (current && typeof current === "object") {
nestedKeysArrays.unshift(Object.keys(current));
path.unshift(current);
done = false;
break;
}
// Change the "dgraph.type" key to "type". This is where the real
// work is done. Everything else is just navigation.
if (key === "dgraph.type") {
path[0].type = path[0]["dgraph.type"];
delete path[0]["dgraph.type"];
}
}
if (done) {
// Remove the array of keys from the list of keys to be processed.
nestedKeysArrays.shift();
// Return to the previous object.
path.shift();
}
}
return result;
};
};
The function middlewares a resolver. I'm calling it before constructing ApolloServer on every resolver in the project, and using it's result in lieu of the original resolver.
The issue can be more succinctly solved if you don't avoid recursion, but then you would be using recursion. Good luck in your future endeavors.

graphql after running mutation if I quickly goback to Previous page, occur error

after running mutation using the graphql, if I quickly goback to Previous page,
occur error : Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in %s.%s, a useEffect cleanup function,
I think it's because I quickly go to another page during the mutation.
If this is not the case, there is no error.
(Even if an error occurs, update succeeds. but I'm worried about errors)
Even if move to another page during mutating, I want to proceed with the update as it is
How can I proceed with the update?
if If there is no way, is there method that How to create a delay during mutating
im so sorry. my english is not good.
const CalendarTodo = ({
month,
day,
data,`enter code here`
isImportWhether,
setIsImportWhether
}) => {
const [value, setValue] = useState("");
const monthDay = `${month + 1}월 ${day}일`;
const [createToDoMutation] = useMutation(CREATE_TODO, {
variables: {
toDoId:
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos.filter(object => object.monthDay === monthDay)[0].id,
monthDay: monthDay,
dayToDo: value,
importEvent: isImportWhether
},
update: (proxy, { data: { createToDo } }) => {
const data = proxy.readQuery({ query: SEE_TODO_OF_ME });
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos
.filter(object => object.monthDay === monthDay)[0]
.dayToDo.push(createToDo);
proxy.writeQuery({ query: SEE_TODO_OF_ME, data });
},
optimisticResponse: {
createToDo: {
__typename: "DayToDo",
id: Math.random().toString(),
toDoList: value,
importEvent: isImportWhether
}
}
});
return (
<>
);
};
export default CalendarTodo;
As you already guessed the reason is the asynchronous request that keeps on running even after un-mounting the component due to navigating away from it.
There are many ways to solve this. One is to add a check whether or not the component you are calling the async request from is still mounted and only update its state if so, e.g.:
useEffect(() => {
let isMounted = true;
apollo.mutate({query, variables, update: {
if(isMounted) {
// update state or something
}
})
return () => {
isMounted = false;
};
}, []);
This way however the data might be lost. If you want to make sure that you receive and store the return value you should add the request to a higher level component or context hat will not be unmounted on navigation. This way you can trigger the async call but dont have to worry about navigating away.

React-Redux re-render on dispatch inside HOC not working

I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.
I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.
My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.
The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.
My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.
It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?
The HomePage HOC:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'
// unconnected stateless react component
const HomePage = (props) => (
<div>
{ !props.auth
? <AnonymousPartialPage />
: <AuthenticatedPartialPage /> }
</div>
);
const mapStateToProps = (state) => {
const store = createStore(
combineReducers({
auth: authReducer
})
);
// When the user logs in, in the Anonymous component, the local storage is set with the response
// of the API when the log in attempt was successful.
const storageAuth = JSON.parse(localStorage.getItem('auth'));
if(storageAuth !== null) {
// Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
store.dispatch(logOutOfApi());
// Make sure the auth info in local storage is contained in the state.auth object.
store.dispatch(loggedIntoApi(...storageAuth))
}
return {
auth: state.auth && state.auth.jwt && storageAuth === null
? state.auth
: storageAuth
};
}
export default connect(mapStateToProps)(HomePage);
with the Anonymous LOC being:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';
export class AnonymousPartialPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
const loginData = { ... };
// This is where I thought the problem initially occurred as I
// would get an error that `this.props` was undefined in the final
// then` of the `fetch`. After doing this, however, the error went
// away and I can see that `props.dispatch is no longer undefined
// when using it. Now though, nothing happens.
const props = this.props;
fetch('https://.../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data && data.jwt) {
props.dispatch(loggedIntoApi(data));
localStorage.setItem('auth', JSON.stringify(data));
}
// else show an error on screen
});
};
render() {
return (
<div>
... onSubmit gets called successfully somewhere in here ...
</div>
);
}
}
export default connect()(AnonymousPartialPage);
the action:
// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
type: 'LOGGED_INTO_API',
auth: auth_token
});
// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
type: 'LOG_OUT_OF_API'
});
and finally the reducer:
const authDefaultState = { };
export default (state = authDefaultState, action) => {
switch (action.type) {
case 'LOGGED_INTO_API':
// SOLUTION : changed this line "return action.auth;" to this:
return { ...action.auth, time_stamp: new Date().getTime() }
case 'LOG_OUT_OF_API':
return { auth: authDefaultState };
default:
return state;
}
};
My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().
Essentially:
x = {
foo:bar
}
x.foo = "baz"
dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!
Instead I created a new object:
x = {
foo:"bar"
}
y = JSON.parse(JSON.stringify(x)) // creates an entirely new object
dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender
// BE CAREFUL OF THE FOLLOWING!
y = x
dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender
Hope this helps!

Resources