I have the following function to check the users
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem("token");
const email = localStorage.getItem("email");
if (token === undefined) {
dispatch(logout());
} else {
const expirationDate = new Date(localStorage.getItem("expirationDate"));
if (expirationDate <= new Date()) {
dispatch(logout());
} else {
dispatch(authSuccess(email, token));
dispatch(
checkAuthTimeout(
(expirationDate.getTime() - new Date().getTime()) / 1000
)
);
}
}
};
};
And for that i did the special initialization function that will take all the needed functions and dispatch them
initialState = {
initialized: false
};
const appReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZED_SUCCESS:
return {
...state,
initialized: true
}
default:
return state;
}
}
export const initializedSuccess = () => ({type: INITIALIZED_SUCCESS});
export const initializeApp = () => (dispatch) => {
let promise = dispatch(authCheckState());
Promise.all([promise])
.then(() => {
dispatch(initializedSuccess());
});
}
And after that i am putting initialization function to the App's componentDidMount function
componentDidMount() {
this.props.initializeApp();
}
and after i am doing that
const mapStateToProps = (state) => ({
initialized: state.app.initialized
})
but after that i am still getting the data too late and i have to refresh the page to see the data. How to implement this feature?
You should fetch data in the component itself. Use Redux for storing the data so that you can use it later or manipulate it. (If you need to do so!)
This how the process should be,
Make an API request on componentDidMount.
Store the data in redux.
Use that data in the component coming in the form of props using Redux.
Related
I created a Todo application in React.js. I have an API to store my todos and their states.
In my application I can drag and drop the todos in the state I want and I can also CRUD the states.
For the state part I use React-Redux.
My problem happens when adding a new state, I would like to get the id that is provided by the API but in my reducer I get this error: A non-serializable value was detected in the state, in the path: stateReducer.
If I don't get the id, I can't delete the state cause I delete by id, I have to reload the page.
To initialize my state list I use a middleware to do the asymmetric part but I don't understand how to do it when I add a new object.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import Api from "../api/Api.ts";
const api = new Api("http://localhost:3333/api/state/")
const initialState = {
status:'idle',
list:[],
error: null
}
export const StateSlice = createSlice({
name:'status',
initialState,
reducers: {
add: async (state, action) => {
let item
await api.post(action.payload).then(state => item = state) // PROBLEME
state.list.push(item)
},
remove:(state, action) => {
state.list.splice(state.list.indexOf(action.payload),1)
api.del(action.payload)
},
update:(state, action) => {
api.update(action.payload)
},
get: async (state,action) => {
state.list = action.payload
},
},
extraReducers(builder) {
builder
.addCase(fetchPosts.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded'
state.list = state.list.concat(action.payload)
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
const getPosts = async () => {
const states = []
let posts = await api.get()
posts.forEach(p => {
states.push({id:p.id,name:p.name})
});
return states
}
export const fetchPosts = createAsyncThunk('state/fetchPosts', async () => {
let response = await getPosts()
return response
})
export const {add, remove, update, get} = StateSlice.actions
export default StateSlice.reducer;
Do I need to create another middleware for this and how call it ? I am new to this technology, can you give me a hint please?
I have a useEffect() hook that I'd like to only update when the data in a state-full array changes. I know I can add that array as a dependency, but my issue is the array is not declared in the same file, it's coming from a redux store. Right now I removed the dependency and It works, but the dev tools show me that it is constantly sending new requests to the server when it should send a request only when submitted.
This is the function in the redux store:
/client/src/store/utils/thunkCreators.js
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
How do I import the conversations from the redux store as a dependency?
Home.js
useEffect(() => {
fetchConversations();
},[X]);
/client/src/store/utils/conversations.js
const GET_CONVERSATIONS = "GET_CONVERSATIONS";
export const gotConversations = (conversations) => {
return {
type: GET_CONVERSATIONS,
conversations,
};
};
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_ONLINE_USER: {
return addOnlineUserToStore(state, action.id);
}
case REMOVE_OFFLINE_USER: {
return removeOfflineUserFromStore(state, action.id);
}
case SET_SEARCHED_USERS:
return addSearchedUsersToStore(state, action.users);
case CLEAR_SEARCHED_USERS:
return state.filter((convo) => convo.id);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
i have this API helper in my react native code, but i want the user to logout everytime the jwtt token isExpired
this is the code
const apiHelper = async (key, payload) => {
let token = await getToken();
//to get the currentToken from AsyncStorage
const isTokenExpired = checkToken(token);
//checkToken return a boolean (it is work just fine)
const dispatch = useDispatch();
if (isTokenExpired) {
dispatch(logout());
return;
}
//... rest of the API Helper
}
result of this code =>
invalid hook call : Hooks can only be called inside the body of a
function component.
what have i tried is calling the redux directly from the store
import store from './src/redux/store';
const apiHelper = async (key, payload) => {
let token = await getToken();
//to get the currentToken from AsyncStorage
const isTokenExpired = checkToken(token);
//checkToken return a boolean (it is work just fine)
let redux = store();
if (isTokenExpired) {
const { store } = redux;
store.dispatch(logout());
return;
}
//... rest of the API Helper
}
this code have no error but the state not changed to logout
this is the logout action
export const logout = () => ({type: 'AUTH_RESET'});
and the auth reducer
const initialState = () => ({
userInfo: {},
token: null,
isLogin: false,
showOnboard: true,
});
const Fetch = (state = initialState(), action = {}) => {
switch (action.type) {
case 'LOGIN_SUCCESS':
const { token, ...userInfo } = action.payload;
return {...state, userInfo, token, isLogin: true, showOnboard: null};
case 'AUTH_RESET':
const init = initialState()
init.showOnboard = false
return {...state, ...init};
default:
return state;
}
};
export default Fetch;
store.js
export default () => {
let store = createStore(persistedReducer, applyMiddleware(logger, thunk));
let persistor = persistStore(store);
return {store, persistor};
};
the logout action and the reducer work fine if i call it using hooks.
but i can't get it work inside an async function.
how can i dispatch the logout inside an async function ?
You should have only one store in your application. This line looks suspicious: let redux = store();
Try to import you store and call store.dispatch(action) on it.
Need help in getting response from a function written inside reducer function
functional component
import {
getAssets,
} from '../../reducers';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = () => {
dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In my reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case ASSETS_LIST: {
return {
...state,
successToast: true,
isLoading: false,
data: action.payload,
};
}
}
export const listsDispactcher = () => dispatch => {
dispatch({ type: SHOW_LOADER });
performGet(ENDPOINT URL)
.then(response => {
debugger;
const payload = response.data;
dispatch({
type: ASSETS_LIST,
payload: {
...payload,
data: payload.results,
},
});
dispatch({ type: HIDE_LOADER });
})
.catch(err => {
dispatch({ type: GET_ASSETS_ERROR, payload: err });
);
});
};
when i click the link ,am getting my api called in function in reducer and its getting response in newtwork tab in developer console , but how to get the response (that is successToast,data,isLoading )in my functional component and to pass the same to child components ?
I advice you to change the structure of your project. Place all your network calls in a file and call them from your component. It is better for readability and understandability
import {
getAssets,
} from './actions';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = async () => {
const data = await dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In ./actions.js
const getAssets =()=>async dispatch =>{
const res = await axios.get();
dispatch(setYourReduxState(res.data));
return res.data;
}
Now your component will get the data of network call. and Your redux state also will get update
For functional components, to access state stored centrally in redux you need to use useSelector hook from react-redux
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
Official doc:
https://react-redux.js.org/api/hooks#useselector-examples
Also found this working example for you to refer.
https://codesandbox.io/s/8l0sv
I need to fetch my data in two different ways and render it according to this. At the first load, I need to fetch all the items one by one and increment the count. After that, I need to fetch all the data at once and update the display. So, I wrote something like this (not the actual code but almost the same thing):
import React, { useEffect } from "react";
import axios from "axios";
import { useGlobalState } from "./state";
const arr = Array.from(Array(100), (x, i) => i + 1);
function App() {
const [{ posts }, dispatch] = useGlobalState();
useEffect(() => {
const getInc = () => {
arr.forEach(async id => {
const res = await axios(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
dispatch({
type: "INC",
payload: res.data
});
});
};
const getAll = async () => {
const promises = arr.map(id =>
axios(`https://jsonplaceholder.typicode.com/posts/${id}`)
);
const res = await Promise.all(promises);
dispatch({
type: "ALL",
payload: res.map(el => el.data)
});
};
if (!posts.length) {
getInc();
} else {
getAll();
}
}, [dispatch]);
return (
<>
<div>{posts.length}</div>
</>
);
}
export default App;
I'm simply using Context and useReducer to create a simple store. The above code works as it is but I skip adding posts.length dependency and this makes me think that my logic is wrong.
I tried to use refs to keep track the initialization state but I need to track the data at every route change. Then, I tried to keep it by adding an init state to my store but I couldn't make it work without problems. For example, I can't find a suitable place to dispatch the init. If I try it after a single fetch it triggers the initialization immediately and my other function (getAll) is invoked.
If anyone wants to play with it here is a working sandbox: https://codesandbox.io/s/great-monad-402lb
I added init to your store:
// #dataReducer.js
export const initialDataState = {
init: true,
posts: []
};
const dataReducer = (state, action) => {
switch (action.type) {
case 'ALL':
// init false
return { ...state, posts: action.payload };
case 'INC':
return { ...state, init: false, posts: [...state.posts, action.payload] };
...
}
// #App.js
function App() {
const [{ init, posts }, dispatch] = useGlobalState();
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
...
}