Handling Auth State using Redux - reactjs

I have a chat-app that uses React, Redux and Firebase. I'm also using thunkmiddleware to do the async updates of the state with Firebase.
I successfully get everything I need, except that everything depends of a previously hard-coded variable.
The question is, how can I call inside my ActionCreators the getState() method in order to retrieve a piece of state value that I need in order to fill the rest of my states?
I currently have my auth: { uid = 'XXXZZZYYYY' }... I just need to call that like
getState().auth.uid
however that doesn't work at all.
I tried a lot of different questions, using mapDispatchToProps, etc. I can show my repo if needed.
Worth to mention that I tried following this other question without success.
Accessing Redux state in an action creator?
This is my relevant current code:
const store = createStore(
rootReducer,
defaultState,
applyMiddleware(thunkMiddleware));
And
function mapDispatchToProps(dispatch) {
watchFirebase(dispatch); // to dispatch async Firebase calls
return bindActionCreators(actionCreator, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(AppWrapper);
Which I am exporting correctly as many other not pure functions work correctly.
For instance, this works correctly:
export function fillLoggedUser() {
return (dispatch, getState) => {
dispatch({
type: C.LOGGED_IN,
});
}
}
However as suggested below, this doesn't do a thing:
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};

In general your thunked action creator should look something like the below (I have used a post id as an example parameter):
const getPost = ( postId ) => ( dispatch, getState ) => {
const state = getState();
const authToken = state.reducerName.authToken;
Api.getPost(postId, authToken)
.then(result => {
// where postRetrieved returns an action
dispatch(postRetrieved(result));
});
};
If this is similar to what you have then I would log your state out and see what is going on with a simple thunk.
const logState = () => ( dispatch, getState ) => {
console.log(getState());
};

Related

How can I cache data that I already requested and access it from the store using React and Redux Toolkit

How can I get data from the store using React Redux Toolkit and get a cached version if I already requested it?
I need to request multiple users for example user1, user2, and user3. If I make a request for user1 after it has already been requested then I do not want to fetch user1 from the API again. Instead it should give me the info of the user1 from the store.
How can I do this in React with a Redux Toolkit slice?
Edit: This answer predates the release of RTK Query which has made this task much easier! RTK Query automatically handles caching and much more. Check out the docs for how to set it up.
Keep reading if you are interested in understanding more about some of the concepts at play.
Tools
Redux Toolkit can help with this but we need to combine various "tools" in the toolkit.
createEntityAdapter allows us to store and select entities like a user object in a structured way based on a unique ID.
createAsyncThunk will create the thunk action that fetches data from the API.
createSlice or createReducer creates our reducer.
React vs. Redux
We are going to create a useUser custom React hook to load a user by id.
We will need to use separate hooks in our hooks/components for reading the data (useSelector) and initiating a fetch (useDispatch). Storing the user state will always be the job of Redux. Beyond that, there is some leeway in terms of whether we handle certain logic in React or in Redux.
We could look at the selected value of user in the custom hook and only dispatch the requestUser action if user is undefined. Or we could dispatch requestUser all the time and have the requestUser thunk check to see if it needs to do the fetch using the condition setting of createAsyncThunk.
Basic Approach
Our naïve approach just checks if the user already exists in the state. We don't know if any other requests for this user are already pending.
Let's assume that you have some function which takes an id and fetches the user:
const fetchUser = async (userId) => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
return res.data;
};
We create a userAdapter helper:
const userAdapter = createEntityAdapter();
// needs to know the location of this slice in the state
export const userSelectors = userAdapter.getSelectors((state) => state.users);
export const { selectById: selectUserById } = userSelectors;
We create a requestUser thunk action creator that only executes the fetch if the user is not already loaded:
export const requestUser = createAsyncThunk("user/fetchById",
// call some API function
async (userId) => {
return await fetchUser(userId);
}, {
// return false to cancel
condition: (userId, { getState }) => {
const existing = selectUserById(getState(), userId);
return !existing;
}
}
);
We can use createSlice to create the reducer. The userAdapter helps us update the state.
const userSlice = createSlice({
name: "users",
initialState: userAdapter.getInitialState(),
reducers: {
// we don't need this, but you could add other actions here
},
extraReducers: (builder) => {
builder.addCase(requestUser.fulfilled, (state, action) => {
userAdapter.upsertOne(state, action.payload);
});
}
});
export const userReducer = userSlice.reducer;
But since our reducers property is empty, we could just as well use createReducer:
export const userReducer = createReducer(
userAdapter.getInitialState(),
(builder) => {
builder.addCase(requestUser.fulfilled, (state, action) => {
userAdapter.upsertOne(state, action.payload);
});
}
)
Our React hook returns the value from the selector, but also triggers a dispatch with a useEffect:
export const useUser = (userId: EntityId): User | undefined => {
// initiate the fetch inside a useEffect
const dispatch = useDispatch();
useEffect(
() => {
dispatch(requestUser(userId));
},
// runs once per hook or if userId changes
[dispatch, userId]
);
// get the value from the selector
return useSelector((state) => selectUserById(state, userId));
};
isLoading
The previous approach ignored the fetch if the user was already loaded, but what about if it is already loading? We could have multiple fetches for the same user occurring simultaneously.
Our state needs to store the fetch status of each user in order to fix this problem. In the docs example we can see that they store a keyed object of statuses alongside the user entities (you could also store the status as part of the entity).
We need to add an empty status dictionary as a property on our initialState:
const initialState = {
...userAdapter.getInitialState(),
status: {}
};
We need to update the status in response to all three requestUser actions. We can get the userId that the thunk was called with by looking at the meta.arg property of the action:
export const userReducer = createReducer(
initialState,
(builder) => {
builder.addCase(requestUser.pending, (state, action) => {
state.status[action.meta.arg] = 'pending';
});
builder.addCase(requestUser.fulfilled, (state, action) => {
state.status[action.meta.arg] = 'fulfilled';
userAdapter.upsertOne(state, action.payload);
});
builder.addCase(requestUser.rejected, (state, action) => {
state.status[action.meta.arg] = 'rejected';
});
}
);
We can select a status from the state by id:
export const selectUserStatusById = (state, userId) => state.users.status[userId];
Our thunk should look at the status when determining if it should fetch from the API. We do not want to load if it is already 'pending' or 'fulfilled'. We will load if it is 'rejected' or undefined:
export const requestUser = createAsyncThunk("user/fetchById",
// call some API function
async (userId) => {
return await fetchUser(userId);
}, {
// return false to cancel
condition: (userId, { getState }) => {
const status = selectUserStatusById(getState(), userId);
return status !== "fulfilled" && status !== "pending";
}
}
);

where is dispatch coming from in this line? export const setAlert = (msg, alertType,timeout=5000) =>dispatch =>

I came across this code. He/she Is creating an action for redux in react-redux app. I don't understand where dispatch is coming from . Can you explain please also is this a good practice?
import uuid from 'uuid';
import {SET_ALERT,REMOVE_ALERT} from './types';
export const setAlert = (msg, alertType,timeout=5000) =>dispatch =>{
const id = uuid.v4();
dispatch ({
type:SET_ALERT,
payload:{msg,
alertType,
id}
});
setTimeout(()=>dispatch({type:REMOVE_ALERT,payload:id}),timeout)}
I guess it is using redux-thunk, which works as a middleware. setAlert returns the function, which is being called with the dispatch as first parameter of that function. It maybe helps more to understand if you clean it up a bit
export const setAlert = (msg, alertType,timeout=5000) => {
const id = uuid.v4();
return (dispatch) => {
dispatch ({
type:SET_ALERT,
payload:{
msg,
alertType,
id
}
});
};
I generally use it in cases I need to work with async functions inside an action, such as ajax requests.
It is coming from mapDispatchToProps:
// action
export const action = (params) => async (dispatch) => // it can be async btw
actionCodeHere()
// component
import {action as importedAction} from 'action'
const component = ({action}) => {
React.useEffect(() => { action(params) }, [])
return <div/>
}
const mapDispatchToProps = {
action: importedAction
} // just object
export default connect(null, mapDispatchToProps)(component)
Is it good practice?
I think it's better than many other ways I saw (redux saga especially, senseless and merciless), this has less code than when mapDispatchToProps is a function.
Easier is only to import dispatch from store directly in actions file and not using mapDispatchToProps at all, but I never saw this approach, seems like it is not good practice.

next.js mapStateToProps, mapDispatchToProps and getInitialProps

i am currently still trying to wrap my head around redux when using next.js and i am not sure what is the best way to use redux with next. I am used to using mapDispatchToProps for my actions and mapStateToProps for my props. After some research i am now using next-redux-wrapper in my _app.js like recommended but now i am fighting with how to best get my props and dispatch my actions. I had look at a few examples and practices and now have a counter component based on one of these examples.
class Counter extends Component {
increment = () => {
const {dispatch} = this.props
dispatch(incrementCount())
}
decrement = () => {
const {dispatch} = this.props
dispatch(decrementCount())
}
reset = () => {
const {dispatch} = this.props
dispatch(resetCount())
}
render () {
const { count } = this.props
return (
<div>
<h1>Count: <span>{count}</span></h1>
<button onClick={this.increment}>+1</button>
<button onClick={this.decrement}>-1</button>
<button onClick={this.reset}>Reset</button>
</div>
)
}
}
function mapStateToProps (state) {
const {count} = state.counter;
return {count};
}
export default connect(mapStateToProps)(Counter)
Most examples i have seen so far do something similar to this or only dispatch actions in getInitialProps. Is there a reason to do it this way and not use mapDispatchToProps?
Cause this work perfectly fine as well:
export default connect(null, {authenticate})(Signin);
Dispatching actions in getIntialProps seems to have some drawback (or i made some mistakes), cause they do not get executed again when the props change. In my user-profile component i get the current user based on a token from the redux store like this:
const Whoami = ({isAuthenticated, user}) => (
<Layout title="Who Am I">
{(isAuthenticated && user && <h3 className="title is-3">You are logged in as <strong className="is-size-2 has-text-primary">{user}</strong>.</h3>) ||
<h3 className="title is-3 has-text-danger ">You are not authenticated.</h3>}
</Layout>
);
Whoami.getInitialProps = async function (ctx) {
initialize(ctx);
const token = ctx.store.getState().auth.token;
if(token) {
const response = await axios.get(`${API}/user`, {headers: {
authorization: token
}});
const user = response.data.user;
return {
user
};
}
}
const mapStateToProps = (state) => (
{isAuthenticated: !!state.auth.token}
);
export default connect(mapStateToProps)(Whoami);
This works perfectly fine for the initial page-load or when navigating there one the client, but when the token expires or i logout the page does not reflect that without reload or navigating there again without my mapStateToProps. But it seems super clunky to split the concern over 2 seperate functions. But i cant find a cleaner way to do it.
Thanks in advance
About mapDispatchToProps:
It is better to use mapDispatchToProps at least because it is easier to test: you can just pass a mock function to your component. With using this.props.dispatch to dispatch some imported actions it can be much harder.
About getInitialProps:
This answer may be helpful:
GetInitialProps: is provided by Next.js and it is NOT always triggered, so be careful with that, it happen when you wrap 1 component inside another. If the parent Component has GetInitialProps, the child's GetInitialProps will never be triggered, see this thread for more info.
I found some answers to my questions after playing around with next a bit more. For pages where the data does not change after intial load, i could get rid of mapStateToProps by rewriting my thunks a bit to return the dispatches and only use getInitialProps like this:
export function fetchShow(id) {
return (dispatch) => {
dispatch({ type: actionTypes.FETCH_SHOW_REQUESTED,id});
// we need to return the fetch so we can await it
return fetch(`http://api.tvmaze.com/shows/${id}`)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
//dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((data) => dispatch({type: actionTypes.FETCH_SHOW_SUCEEDED,id, show: data, time: Date.now() }))
.catch(() => dispatch({ type: actionTypes.FETCH_SHOW_ERROR,id }));
};
}
Post.getInitialProps = async function ({store, isServer, pathname, query}) {
const { id } = query;
const {show} = await store.dispatch(fetchShow(id));
return {show};
}
For pages where the data should update upon store changes i am not sure yet. My current idea is to try and write a helper function that will be called from both getInitialProps and mapStateToProps to reduce code duplication but i am not sure yet.

How to pass argument to React Redux middleware inside mapDispatchToProps

The situation is I am creating a single board which will hold a collection of note cards (each note has an id, title and body), and each note card will have a button to delete it. Also the application will be syncing with firebase, so my main question is how to pass arguments to middlewares AND do it inside of mapDispatchToProps. The following is my code to point out where my success with middleware and where I am currently blocked.
To hydrate the app on startup, I dispatch a middleware function that gets the data from firebase, and then dispatches actions handled by reducers and finally gets updated by the container/presentation component.
Middleware function:
export function hydrateApp(dispatch) {
dispatch({type: 'PENDING'});
fireBaseDBRef.once('value').then(snapshot => {
let firebaseNotes = snapshot.val()
let notes = [];
// populate notes using firebaseNotes, nothing exciting
dispatch({ type: 'DONE', notes: notes });
// the 'DONE' action.type is handled by the reducer and passes data
// to the container component successfully
}).catch(e => {
dispatch({type: 'ERROR', error: e});
});
}
Container component:
const mapStateToProps = state => {
return {
notes: state.boardReducer.notes
};
};
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
}
};
};
const BoardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(BoardPresentation);
So far so good, and this is what I added to the same middleware and container component files to handle delete scenarios.
Middleware function:
export function deleteNote(id) {
return (dispatch) => {
dispatch({type: 'PENDING'});
//firebase stuff happening here
dispatch((type: 'DONE'});
}
}
Container component:
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
},
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote(id));
}
};
};
The problem is that deleteNote gets called non-stop on startup, I don't even need to click the button.
I know the code presented may not make a whole bunch of sense, but the crux of my problem is that I need to some how pass an id to the middleware function when the user clicks on the button, and because I'm passing the function as a prop, it for some reasons decides to just call it a million times.
I could call boardMiddleware.deleteNote function inside the presentation component just like the examples in the official redux page do, but I'm wondering if there is a way of doing it the way I'm trying to do.
I also thought about binding the argument into the middleware function, but that also doesn't feel right, something like this
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote.bind(id));
}
Thanks for any help in advance!

react-redux not dispatching thunk api call

I'm taking a working web version with redux and Api calls and porting them to a React Native app. However I notice when trying to dispatch a thunk to make an API call, I can't seem to see a console log in my thunk to confirm the dispatch. This makes me think something is not connected properly but I just don't see what that is. What am I missing?
I create a store with an initial state: When I log store.getState() everything looks fine.
const initialState = {
config: fromJS({
apiUrl: "http://localhost:3000/account-data",
})
}
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(thunk),
)
)
I use mapDispatchToProps and I see the functions in my list of props
export function mapDispatchToProps(dispatch) {
return {
loadProducts: () => dispatch(loadProducts())
};
}
However, when I inspect my loadProducts function, I do not see a console log confirming the dispatch. What's going on here? Why is loadProducts not dispatching? On the web version I'm able to confirm a network request and logs. On React Native I do not see a network request or these console logs.
export function loadProductsCall() {
console.log('in RN loadProductsCall') //don't see this
const opts = constructAxpOpts();
return {
[CALL_API]: {
types: [
LOAD_REQUEST,
LOAD_SUCCESS,
LOAD_FAILURE
],
callAPI: (client, state) =>
client.get(`${state.config.get('apiUrl')}/members`, opts),
shouldForceFetch: () => false,
isLoaded: state => !!(state.core.resources.products.get('productsOrder') &&
state.core.resources.products.get('productsOrder').length),
getResourceFromState: (state) => state.core.resources.products.toJS(),
isLoading: state => !!state.core.resources.products.get('isLoading'),
getLoadingPromise: state => state.core.resources.products.get('loadingPromise'),
payload: {}
}
};
}
export function loadProducts() {
console.log('in loadProducts') //don't see this
return (dispatch) =>
console.log('in loadProducts dispatched 2') //don't see this either
dispatch(loadProductsCall())
.then((response) => {
return response;
});
}
This code is missing custom API middleware that handles three action types. Also, in mapDispatchToProps a function is wrapping the dispatch. This function need to either be unwrapped and return a promise or called somewhere else in the code.

Resources