I'm sorry if my code is extremely amateur. I just recently started working with react-redux and javascript. I feel like I am a bit spaced out when looking at tutorials when it comes to visualizing states and what the store does. So I'm trying to code something to visually see it in code. My code is similar to the default preloaded code from Visual Studio when a new project for ASP.NET Web app w/ react-redux is created. I want to just call an API using redux and pass that information to the page.
For what I've got right now, I noticed that my API is not called, as putting a red light on my actionCreator is not reached.
///////////File 1 CreaterAction and Reducer/////////////////
const requestEvents = 'REQUEST_EVENTS';
const recieveEvents = 'RECEIVE_EVENTS';
const initialState = { events: [], isLoading: false };
async function getData(url) {
const response = await fetch(url);
return response.json();
}
export const actionCreators = {
requestEvents: () => async (dispatch) => {
dispatch({ type: requestEvents});
const url = 'api\stuff';
const events = await getData(url);
dispatch({ type: recieveEvents, events });
}
};
export const reducer = (state, action) => {
state = state || initialState;
if (action.type === requestEvents) {
return {
...state,
events: action.events,
isLoading: true
};
}
return state;
};
//////////////File 2 My Actual component//////////////////
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { actionCreators } from '../store/file1';
class Events extends Component {
componenetDidMount() {
this.ensureDataFetched();
}
ensureDataFetched() {
const stuff = this.props.//!!!! Does not show .requestEvents
}
render() {
return (
<div>
<h1>Events</h1>
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{renderData(this.props)}
</div>
);
}
}
function renderData(props) {
return (
<p>{props.events}</p>
);
}
export default connect(
state => state.events,
dispatch => bindActionCreators(actionCreators, dispatch)
)(Events);
///////////////FILE 3 Config Store//////////////////
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Counter from './Counter';
import * as file1 from './file1';
export default function configureStore (history, initialState) {
const reducers = {
events: file1.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
It runs just shows
Events
This component demonstrates fetching data from the server and working with URL parameters.
My guess is that it's because I haven't used/called this.props.requestEvents
Related
I am starting a new next js project,we are migrating from a normal react app to a next app.
we intend to use redux toolkit for our global state management and we are using server side rendering.so we came across next-redux-wrapper npm package which looked like it solves most of our problems with redux and ssr but form some reason when ever we use server side rendering on one of the pages the HYDRATE action from next-redux-wrapper is getting called atleast twice sometimes even 4 times.What exactly is going wrong because the article i referred to seems to work fine,i have attached my Redux store details and the Redux slice details,and my getServerSideProps function details.
import { createStore, applyMiddleware, combineReducers } from "redux";
import count from "../ReduxSlices/CounterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
import { configureStore } from "#reduxjs/toolkit";
const combinedReducer = combineReducers({
count,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
count: {
count: state.count.count + action.payload.count.count,
},
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer: reducer,
});
export const wrapper = createWrapper(makeStore, { debug: true });
and my Redux slice is
import { createSlice } from "#reduxjs/toolkit";
import { HYDRATE } from "next-redux-wrapper";
const initialState = {
count: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState: initialState,
reducers: {
increment: (state) => {
state.count = state.count + 1;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
and finally this is how i dispatch an action inside getServerSideProps
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async () => {
store.dispatch(increment());
console.log("server", new Date());
}
);
The console log is logging only once but the HYDRATE action is getting dispatched atleast two times...any insight will be helpful,thank you.
I had the same issue. Disabling react strict mode worked for me.
const nextConfig = {
// reactStrictMode: true,
...
}
module.exports = nextConfig
I've created a react app using create-react-app and react-redux. I dispatch an action using mapDispatchToProps on clicking a button and it returns a payload. But when I try to retrieve the props using mapStateToProps in my component, it returns the initial state.
What am I doing wrong?
I have tried to debug thoroughly and I realize that the action is dispatched and the payload makes it to the action creator. But the reducer isn't triggered after the action is dispatched.
It might be how I am calling it or how I have set up my reducer as well.
index.js file:
import React from 'react';
import './css/index.css';
import App from './App/App';
import * as serviceWorker from './serviceWorker';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
reducers/Reducer.js
reducers/Reducer.js
import { GET_RESP } from '../modules/actions'
function getInitialState () {
return {
predictions: [],
score: null
}
}
function gesResp (state, payload) {
return {
...state,
predictions: payload.predictions,
score: payload.score
}
}
export function Reducer (state, action) {
if (!state) {
return getInitialState();
}
switch(action.type) {
case GET_RESP:
return gesResp(state, action.payload);
default:
return state;
}
}
export default Reducer;
reducers/index.js
import { combineReducers } from 'redux';
import Reducer from './Reducer'
const rootReducer = combineReducers({
Reducer
});
export default rootReducer;
action.js
import axios from 'axios';
// actions:
export const GET_RESP = 'GET_RESP';
// action creators:
export function gesResp (payload) {
return {
type: GET_RESP,
payload: payload
}
}
export function fetchRecommendations (description, resp) {
let url = 'myendpointurl';
let requestPayload = {
description: description,
resp: resp
}
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
gesResp(res.data);
})
}
}
component file: (I'm only posting related code):
handleSubmit () {
this.props.fetchMyRecommendations(Desc,
this.state.htmlContent);
}
const mapStateToProps = state => {
return {
predictions: state.Reducer.predictions,
score: state.Reducer.score
};
}
const mapDispatchToProps = dispatch => {
return {
fetchMyRecommendations: (Desc, userScore) =>
dispatch(fetchRecommendations(Desc, userScore))
};
}
export default connect(mapStateToProps, mapDispatchToProps)
(HomePage);
Ideally what I want is in the mapStateToProps to return the predictions array and the resp score.
I can see that they are being returned in the network call and showing up the actions call as well. Thanks in advance to whoever can help! :)
You need to dispatch getReccommendations to actually trigger the reducer for your asynchronous action. Try the following:
export function fetchRecommendations (job_description, resume) {
let url = 'myendpointurl';
let requestPayload = {
job_description: job_description,
resume: resume
};
return (dispatch) => {
return axios.post(url, requestPayload)
.then(function(res) {
dispatch(getReccommendations(res.data));
});
}
}
Hopefully that helps!
I'm just starting with React and Redux and stumbled upon something I can't figure out by myself - I think that Redux state is not changing and it's causing (some of) errors. I'm checking state with use of remote-redux-devtools#0.5.0.
My code:
Categories.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getCategories } from '../../actions/categories';
export class Categories extends Component {
static propTypes = {
categories: PropTypes.array.isRequired
};
componentDidMount() {
this.props.getCategories();
}
render() {
return (
<div>
Placeholder for categories.
</div>
)
}
}
const mapStateToProps = state => ({
categories: state.categories.categories
});
export default connect(mapStateToProps, { getCategories })(Categories);
../../actions/categories.js:
import axios from "axios";
import { CATEGORIES_GET } from "./types";
export const getCategories = () => dispatch => {
return axios
.get("/api/notes/categories/")
.then(res => {
dispatch({
type: CATEGORIES_GET,
payload: res.data
});
})
.catch(err => console.log(err));
};
reducers/categories.js:
import { CATEGORIES_GET } from '../actions/types.js';
const initialState = {
categories: []
};
export default function (state = initialState, action) {
switch (action.type) {
case CATEGORIES_GET:
return {
...state,
categories: action.payload
};
default:
return state;
}
}
store.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'remote-redux-devtools';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)));
export default store;
reducers/index.js
import { combineReducers } from "redux";
import categories from './categories';
export default combineReducers({
categories,
});
Using remote-redux-devtools, I've never seen anything in my state. Currently this code above gives me 3 errors, two of them being
this.props.getCategories is not a function
My guess is that because there is some issue with Categories class, it's not passing anything to state and it could be root cause of errors. I had one more error, connected to Categories not being called with attributes, but for debug purposes I put empty array there - one error dissapeared, but that's it. I've also tried adding constructor to Categories and called super(), but did not help also.
I believe your issue is that you're exporting your Categories class twice, once connected, the other not.
If you remove export from export class Categories extends Component, does it work as expected?
When you're mapping the state in a component, you must access the desired variable through a reducer.
So instead of:
const mapStateToProps = state => ({
categories: state.categories
});
You must use:
const mapStateToProps = state => ({
categories: state.categories.categories
});
Your props don't have getCategories method, because you didn't pass it as a function to connect.
A better approach is to define only the action code in your actions file and then use mapDispatchToProps.
../../actions/categories.js
import axios from "axios";
export const getCategories = () => {
axios
.get("/api/notes/categories/")
.then(res => res.data);
})
.catch(err => console.log(err));
};
Categories.js
import { getCategories } from '../../actions/categories'
import { CATEGORIES_GET } from "./types";
const mapDispatchToProps = dispatch => {
return {
getCategories: () => dispatch(() => { type: CATEGORIES_GET, payload: getCategories() }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Categories);
I have made login and logout actions and userReducer. How can I integrate AsyncStorage with Redux? I am using Redux Thunk as a middleware.
I am able to implement login and logout using internal state variable but I am not able to understand how to break it down into action and reducer as well as make use of AsyncStorage for storing accessToken.
Original Code:
_onLogin = () => {
auth0.webAuth
.authorize({
scope: 'openid profile',
audience: 'https://' + credentials.domain + '/userinfo'
})
.then(credentials => {
this.setState({ accessToken: credentials.accessToken });
})
.catch(error => console.log(error));
};
_onLogout = () => {
if (Platform.OS === 'android') {
this.setState({ accessToken: null });
} else {
auth0.webAuth
.clearSession({})
.then(success => {
this.setState({ accessToken: null });
})
.catch(error => console.log(error));
}
};
loginAction.js:
import { LOGIN_USER } from './types';
import Auth0 from 'react-native-auth0';
var credentials = require('./auth0-credentials');
const auth0 = new Auth0(credentials);
export const loginUser = () => dispatch => {
auth0.webAuth
.authorize({
scope: 'openid profile',
audience: 'https://' + credentials.domain + '/userinfo'
})
.then(credentials =>
dispatch({
type: LOGIN_USER,
payload: credentials.accessToken
})
)
.catch(error => console.log(error));
}
logoutAction.js:
import { LOGOUT_USER } from './types';
import Auth0 from 'react-native-auth0';
var credentials = require('./auth0-credentials');
const auth0 = new Auth0(credentials);
export const logoutUser = () => dispatch => {
auth0.webAuth
.clearSession({})
.then(success =>
dispatch({
type: LOGOUT_USER,
payload: null
})
)
.catch(error => console.log(error));
}
userReducer.js:
import { LOGIN_USER, LOGOUT_USER } from '../actions/types';
const initialState = {
accessToken: null
}
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
_storeData = async () => {
try {
await AsyncStorage.setItem('accessToken', action.payload);
} catch (error) {
console.log(error)
}
}
return {
...state,
accessToken:action.payload
};
case LOGOUT_USER:
_removeData = async (accessToken) => {
try {
await AsyncStorage.removeItem(accessToken);
} catch (error) {
console.log(error)
}
}
return {
...state,
accessToken:action.payload
};
default:
return state;
}
}
I am new to Redux so I tried converting original code into actions and reducers but I am not sure whether I have implemented AsyncStorage in userReducer.js correctly?
To persist redux state I recommend you redux-persist.
Installation:
npm i -S redux-persist
Usage:
First, configure redux store
// configureStore.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
Then, wrap your root component with PersistGate
import { PersistGate } from 'redux-persist/integration/react'
// ... normal setup, create store and persistor, import components etc.
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
);
};
You can conveniently use AsyncStorage alone OR redux to manage authentication state. Depends on which you are comfortable with. I will give you an example of both.
For AsyncStorage:
Assuming you have authentication keys that is valid for 2 weeks only. You can take note when your user logs in and save the time. eg:
//LoginScreen
import { onSignIn } from '../actions/auth'; //I will describe the onSignInMethod below
import axios from 'axios'; //lets use axios. You may use fetch too.
export default class LoginScreen extends Component {
//your code: state, static etc
loginMethod = () => {
const url = yourauthUrl;
const payload = {
email: this.state.email,
password: this.state.password
};
axios.post(url, payload)
.then((response) => {
if (response.status == 200) {
const dateOfLastLogin = new Date().getTime().toString(); //take note of the time the user logs in.
AsyncStorage.setItem('dateOfLastLogin', dateOfLastLogin);
}
})
.then(() => {
onSignIn() //onSignIn handles your sign in. See below.
.then(() => this.props.navigation.navigate('AfterSignInPage'));
})
.catch(() => { // your callback if onSignIn Fails
});
})
.catch((error) => { //your callback if axios fails
});
}
}
In ../actions/auth.js
import { AsyncStorage } from 'react-native';
export const onSignIn = () => AsyncStorage.setItem('auth_key', 'true');
//in LoginScreen we called this to set that a user has successfully logged in
//why is true a string? -- Because Asyncstorage stores only strings
export const onSignOut = () => AsyncStorage.multiRemove(['auth_key', 'dateOfLastLogin']);
//now lets create a method that checks if the user is logged in anytime
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.multiGet(['auth_key', 'dateOfLastLogin'])
.then((res) => {
const userKey = res[0][1];
const lastLoginDate = parseInt(res[1][1]);
const today = new Date().getTime();
const daysElapsed = Math.round(
(today - lastLoginDate) / 86400000
);
if (userKey !== null && (daysElapsed < 14)) {
resolve(true);
} else {
resolve(false);
}
})
.catch((err) => reject(err));
});
};
now we can import { isSignedIn } from '../actions/auth'; from any of our components and use it like this:
isSignedIn()
.then((res) => {
if (res) {
// user is properly logged in and the login keys are valid and less than 14 days
}
})
////////////////////////////////////////////////////////////////////////////
If you want to use redux
Handling login in redux
In your types.js
//types.js
export const LOGGED_IN = 'LOGGED_IN';
In your redux actions
//loginActions.js
import {
LOGGED_IN,
} from './types';
export function login() {
let dateOfLastLogin = null;
let isLoggedIn = 'false';
AsyncStorage.multiGet(['auth_key', 'dateOfLastLogin'])
.then((res) => {
isLoggedIn = res[0][1];
dateOfLastLogin = parseInt(res[1][1]);
}); //note this works asynchronously so, this may not be a good approach
return {
type: LOGGED_IN,
isLoggedIn,
dateOfLastLogin
};
}
In your loginReducer
//LoginReducer.js
import {
LOGGED_IN
} from '../actions/types';
const initialState = {
userIsLoggedIn: false
};
export function loginReducer(state=initialState, action) {
switch (action.type) {
case LOGGED_IN:
const userKey = action.isLoggedIn;
const lastLoginDate = action.dateOfLastLogin;
const today = new Date().getTime();
const daysElapsed = Math.round(
(today - lastLoginDate) / 86400000
);
let trulyLoggedIn = false;
if (userKey !== null && (daysElapsed < 14)) {
trulyLoggedIn = true;
} else { trulyLoggedIn = false }
return {
userIsLoggedIn: trulyLoggedIn
};
default:
return state;
}
}
In your ./reducers/index.js
//reducers index.js
import { combineReducers } from 'redux';
import { loginReducer } from './LoginReducers';
const rootReducer = combineReducers({
loggedIn: loginReducer
});
export default rootReducer;
In your store where you used redux-thunk, applyMiddleWare. Lets call it configureStore.js
//configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
In your App.js
//App.js
import { Provider } from 'react-redux';
import configureStore from './src/store/configureStore'; //where you configured your store
import { YourMainNavigator } from '../src/config/router'; //where your root navigator is
const store = configureStore();
export default class App extends Component<{}> {
render() {
return (
<Provider store={store}>
<YourMainNavigator />
</Provider>
);
}
}
You should know you no longer need the isSignedIn method in your auth.js
Your login method remains the same as outlined above in LoginScreen.
Now you can use redux to check the state of login like this:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class MyComponent extends Component {
someFunction() {
if (this.props.loggedIn) {
//do something
}
}
}
const mapStateToProps = (state) => {
return {
loggedIn: state.loggedIn.userIsLoggedIn
};
}
export default connect(mapStateToProps)(MyComponent);
There should be a better way of using redux to manage login - better than what I outlined here. I think you can also use redux to manage your login state without using AsyncStorage. All you need to do is in your loginScreen, if the login functions returns a response.status == 'ok', you can dispatch an action to redux that logs the user in. In the example above, using asyncstorage you might only need to use redux to check if a user is logged in.
It is recommended that you use an abstraction on top of AsyncStorage instead of AsyncStorage directly for anything more than light usage since it operates globally. Redux-persist is that abstraction that goes on top of AsyncStorage. It provides a better way to store and retrieve more complex data (e.g. redux-persist has persistReducer(), persistStore()).
React native typescript implementation
storage.ts
import AsyncStorage from "#react-native-community/async-storage";
import { createStore, combineReducers } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import exampleReducer from "./example.reducer";
const rootReducer = combineReducers({
example: exampleReducer,
});
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["example"],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
export { store, persistor };
App.tsx
import React from "react";
import { PersistGate } from "redux-persist/es/integration/react";
import { Provider } from "react-redux";
import RootNavigator from "./navigation/RootNavigator";
import { store, persistor } from "./store";
function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootNavigator />
</PersistGate>
</Provider>
);
}
export default App;
I'm new to React, please keep this in mind.
I'm trying to render a list of recipes fetched from food2fork API but I can't get the view to update, even if the data is fetched correctly.
Here's recipe_list.js:
import React, { Component } from "react";
import { connect } from "react-redux";
class RecipesList extends Component {
// renderRecipe(recipe) {
// console.log(recipe);
// return (
// <div>{recipe}</div>
// );
// }
render() {
console.log("Render function ", this.props.recipes)
return (
<div>
<p>Recipes</p>
<div>{this.props.recipes}</div>
</div>
);
}
}
function mapStateToProps(state){
return { recipes: state.recipes };
}
export default connect(mapStateToProps)(RecipesList);
Here's reducer_recipes.js:
import FETCH_RECIPES from "../actions/index";
export default function(state = [], action){
switch (action.type) {
case FETCH_RECIPES:
return action.payload;
}
return state;
}
Here's /reducers/index.js:
import { combineReducers } from 'redux';
import RecipesReducer from "./reducer_recipes";
console.log(RecipesReducer);
const rootReducer = combineReducers({
recipes: RecipesReducer
});
export default rootReducer;
Here's /actions/index.js:
import axios from "axios";
const API_KEY = "****************************";
export const URL = `https://food2fork.com/api/search?key=${API_KEY}`;
export const FETCH_RECIPES = "FETCH_RECIPES";
export function fetchRecipes(term){
const url = `${URL}&q=${term}`;
const request = axios.get(url);
return {
type: FETCH_RECIPES,
payload: request
};
}
I don't get any specific error. The view just doesn't update. I tried to spread some console.log around the files to try to understand where the problem is.
It seems like the Reducer is not successfully delivering the payload to the component.
NOTE: I'm using react-promise so the promise returned from axios is automatically resolved.
Any ideas?
===================================================================
EDIT:
Thank you for the useful links but there is clearly something that I'm still missing here.
I have modified the action index:
function getStuffSuccess(response) {
return {
type: FETCH_RECIPES,
payload: response
};
}
function getStuffError(err) {
return {
type: ERROR_FETCH_RECIPES,
payload: err
};
}
export function fetchRecipes(term) {
const url = `${URL}&q=${term}`;
return function(dispatch) {
axios.get(url)
.then((response) => {
dispatch(getStuffSuccess(response));
})
.catch((err) => {
dispatch(getStuffError(err));
});
};
}
I have also included redux-thunk to the store:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from "redux-promise";
import Thunk from 'redux-thunk';
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(Thunk, ReduxPromise) (createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('.container'));
The behaviour hasn't changed from before. The view is still not updating.
NOTE: If I console.log the payload from the Reducer the data is in there. But when I try to do the same in the View nothing happens.
Your action is not synchronus here, you need to use Async Action to deliver the response to reducer, meaning, you have to dispatch the response instead of returning it. Check the given link for more details.
I would try refactoring your /actions/index.js like so:
import axios from 'axios';
export const FETCH_RECIPES = 'fetch_recipes';
export const CREATE_RECIPE = 'create_recipe';
const ROOT_URL = '<url-of-api-endpoint>';
const API_KEY = '<api-key>';
export function fetchRecipes() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_RECIPES,
payload: request
};
}
export function createRecipe(values, callback){
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
.then(() => callback());
return {
type: CREATE_RECIPE,
payload: request
}
}