I just started using Redux, as my application got complicated with the passing states to different component. I encountered an error while trying to retrieve data to another components.
I want to pass a state to another component, but I get this errror:
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState().regionId)
})
export default store;
In my Network.js component im calling this
const REGION_ID = store.subscribe(() => {
return store.getState().regionId;
})
console.log(REGION_ID);
I tried the following but that gave me an error also. Many thanks!
const REGION_ID = return store.getState().regionId;
This is not an error. The store.subscribe() method returns an unsubscribe method, and the console.log() displays the implementation of this method:
const REGION_ID = store.subscribe(() => {
return store.getState().regionId; // this is isn't returned to REGION_ID
})
console.log(REGION_ID); // REGION_ID contains the unsubscribe method
If you want to use the the regiondId you get from the state place your code inside the subscribe callback:
tore.subscribe(() => {
const REGION_ID = store.getState().regionId;
console.log(REGION_ID );
})
This line const REGION_ID = return store.getState().regionId; produces an error because you can't return outside of a function.
Related
I want to obtain a value from a variable hosted in redux and memorize it, to later compare it with itself and verify if it is different.
I need, for example, to do the following : const value = useSelector(state => state.data.value); (suppose that here the value is 0) now, when value changes, i need to compare it with the value it had previously
If you want to check what the value was on the previous render, you can save it in a ref:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prevRef = useRef();
useEffect(() => {
// Once the render is complete, update the ref's value
prevRef.current = value;
});
// Do something comparing prevRef.current and value
}
If you're doing this a lot you might find it useful to make a custom hook:
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.curren;t
}
// used like:
const SomeComponent = () => {
const value = useSelector(state => state.data.value)
const prev = usePrevious(value);
// Do something comparing prev and value.
}
You have to use selectors(i use 'reselect' library for that), such as:
file: selectors.js
import { createSelector } from 'reselect';
const stateEnvironments = state => state.environments;
export const selectEnvironments = createSelector([stateEnvironments],
environments => environments.data);
so then in your component you can use mapStateToProps with reselect and connect
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
// your component here
const EnvironmentCurrencies = props => {
const {
data,
} = props;
return (
<div>
...
</div.
);
};
const mapStateToProps = createStructuredSelector({
data: selectEnvironments,
});
// here you can update your values with actions
// mapDispatchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = dispatch => ({
setEnvironment: environment => dispatch(actions.setEnvironment(environment)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Environments);
this is not a full working example and the version that you might use, can have a bit different synaxis, but I hope this gives you a kick start. If not, let me know, I'll add more extensive example
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
I don't know why but I have infinite loop when fetching data in Redux operations.
I have an app with Redux and ReactJS.
This is my React component
const CustomersTable = (props) => {
useEffect( () => {
props.getAllCustomers()
}, []);
return <Table ...props.customers />
}
const mapStateToProps = (state) => ({
customers: state.customers,
})
const mapDispatchToProps = dispatch => ({
getAllCustomers: () => dispatch(getAllCustomers()),
})
export default connect(
mapStateToProps, mapDispatchToProps
)(CustomersTable);
This is getAllInvoices()
const fetchCustomers = async() => {
/**
* I fetch only documents with flag delete==false
*/
const snapshot = await firestore.collection("customers").where('deleted', '==', false).get()
let data = []
snapshot.forEach(doc => {
let d = doc.data();
d.id_db = doc.id
//...other
data.push(d)
})
return data
}
export const getAllCustomers = () =>
async (dispatch) => {
const customers = await fetchCustomers()
// I reset state becouse I wont duplicate inovices in tables
dispatch(actions.reset())
customers.map(customer => dispatch(
actions.fetch(customer)
))
}
And reducers
const customerReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FETCH_CUSTOMERS:
return {
...state, list: [...state.list, action.item]
}
case types.RESET_CUSTOMERS:
return {
...state, list: []
}
default:
return state
}
}
I expect that reducers RESET_CUSTOMERS and then FETCH_CUSTOMERS done job. But it still working in loop reset->customers.
I thought that is still rendered the component in useEffect but I think that hook is writing good.
I tested other reducers which are copy-pase reducers from Customers and they work well.
EDIT 1
#godsenal, thanks for your reply:
actions.js:
import types from './types'
const fetch = item => ({
type: types.FETCH_CUSTOMERS, item
})
const reset = item => ({
type: types.RESET_CUSTOMERS, item
})
export default {
fetch,
reset
}
As regards <Table /> it is AntDesign component (https://ant.design/components/table/). Without that, it looks the same.
EDIT 2
It is incredible. I copied all files from modules (customers) and paste into contracts directory. Then I changed all variables, functions, etc from customer to contract. Now it working (only contracts), but customers infinite loop. Maybe something disturbs in outside a structure.
EDIT 3
I found in app.js that in mapStateToProps I added customers to props. After remove (because I don't need it in root component) it began works fine. I suspect that fetch method in <CustomerTable /> affect the <App /> component and it render in a loop. I discovered that component isn't still updated in a loop, but its mounts and unmounts in a loop.
But still, I don't understand one thing. In <App />, I still have in mapStateToProps dispatching invoice from a store (the same case as customers) and in this case, everything works fine.
I'm trying to handle a form submission to show a loading component when the data fetch is occuring. I'd like to display the data when it's been loaded into my Redux store.
Right now, I've set up my component to use React hooks. While the data loads into my redux store successfully, I'm not sure how to "await" the result of the action being completed. Here's a simplified version of what my component looks like:
const DataPage = (props) => {
const [isLoading, setIsLoading] = useState(false);
const [isError, setError] = useState(false);
useEffect(() => { // Reset Filters when dataSource changes...
setError(false);
setIsLoading(false);
}, [dataSource]);
const handleSubmit = (e, { dataSource }) => {
e.preventDefault();
setError(false)
setIsLoading(true);
//// IDEALLY THIS IS WHERE THE FIX WOULD GO? TURN THIS INTO ASYNC/AWAIT?
props.fetchData({ dataSource, token: localStorage.JWT_TOKEN });
};
return (
<div className="dataPage">
<form className="dataPage__filters" onSubmit={(e) => handleSubmit(e, { dataSource })}>
<DataSelector dataSource={dataSource} setDataSource={setDataSource}/>
<button className="button">
Search
</button>
</form>
{isError && <div>Something went wrong...</div>}
{ isLoading ? ( <div>...Loading </div> ) : (
<div className="dataPage__table">
<DataTable /> // This is connected to my redux-store separately through 'connect'
</div>
)}
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
fetchData: ({ dataSource, token }) => dispatch(startFetchData({ dataSource, token }))
});
export default connect(null, mapDispatchToProps)(DataPage);
The relevant actions (startFetchData, and setData) are located in another file, and look like this:
export const setData = (data) => ({
type: "SET_DATA",
data
});
export const startFetchData = ({ dataSource, filter, filterTarget, token }) => {
return (dispatch) => {
axios.get(`${'http://localhost:8081'}/api/${dataSource}`, { headers: { authorization: token }})
.then((res) => {
dispatch(setData(result));
});
}
};
I'd like to be able to do this without introducing any new dependencies if possible.
A note for those using TypeScript: If you want to await a promise returned by an action using useDispatch() you may see TypeScript complaining about an unnecessary await.
In this case make sure to add the correct typing (see ThunkDispatch) to useDispatch via generics.
Also with useEffect() with async-await syntax make sure to wrap your async code in another closure because useEffect() expects a void return value and Typescript otherwise complains about you returning a Promise.
const dispatch = useDispatch<ThunkDispatch<any, any, Action>>();
useEffect(() => {
(async () => {
const myResult = await dispatch(...);
const anotherResult = await dispatch(...);
// ...
})();
});
I recommend you to use redux-thunk middleware. It's really easy and useful library to able your action to be, instead of objects, functions (including async functions). I'll give you an example:
Store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import api from './services/api';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
// With extra argument, in this case, my API):
applyMiddleware(thunk.withExtraArgument(api));
);
AuthDuck.js
Giving this duck (types, actions and reducers in the same file, see more here)
// ----------------------------------------------------------------
// Types
// ----------------------------------------------------------------
const Types = {
SIGN_IN_START: 'SIGN_IN_START',
SIGN_IN_SUCCESS: 'SIGN_IN_SUCCESS',
SIGN_IN_FAIL: 'SIGN_IN_FAIL'
};
// ----------------------------------------------------------------
// Actions
// ----------------------------------------------------------------
const signin = function (user) {
// LOOK HERE!
// Redux Thunk able you to return a function instead of an object.
return async function (dispatch, getState, api) {
try {
dispatch({ type: Types.SIGN_IN_START });
const token = await api.access.signin(user);
dispatch({ type: Types.SIGN_IN_SUCCESS, payload: token });
} catch (error) {
dispatch({ type: Types.SIGN_IN_FAIL, payload: error });
}
};
};
export const Actions = { signin };
// ----------------------------------------------------------------
// Reducers
// ----------------------------------------------------------------
export default function reducer(state, action) {
switch (action.type) {
case VeasyCalendarTypes.SIGN_IN_START:
return { ...state, isLoading: true };
case VeasyCalendarTypes.SIGN_IN_SUCCESS:
return { ...state, isLoading: false, token: action.payload };
case VeasyCalendarTypes.SIGN_IN_FAIL:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
I hope to helped you, let me know if it worked for your case :)
Best regards
I have a project that uses React + Redux + Thunk and I am rather new to the stack. I have a scenario where I am fetching an array from an API call in my action/reducer, but it is not re-rendering in a component/container that is hooked up to the Store. The component does render the first time when I fire up the app, but at that point the array is undefined when logged to console.
I am trying to display the array's length, so this is always resulting in 0. With ReduxDevTools I see that the state of network_identities does populate correctly and is longer zero... Where am I going wrong?
Here is my sample action
///////////// Sample action /////////////
import axios from 'axios';
const url = 'sample#url.com';
const authorization = 'sample_auth';
export function fetchConnections() {
const params = {
headers: {
authorization,
},
};
return (dispatch) => {
// call returns an array of items
axios.get(`${url}/connection`, params)
.then((connections) => {
let shake_profiles = [];
let connected_profiles = [];
let entity_res;
// map through items to fetch the items data, and split into seperate arrays depending on 'status'
connections.data.forEach((value) => {
switch (value.status) {
case 'APPROVED': case 'UNAPPROVED':
{
axios.get(`${url}/entity/${value.entity_id_other}`, params)
.then((entity_data) => {
entity_res = entity_data.data;
// add status
entity_res.status = value.status;
// append to connected_profiles
connected_profiles.push(entity_res);
});
break;
}
case 'CONNECTED':
{
axios.get(`${url}/entity/${value.entity_id_other}`, params)
.then((entity_data) => {
entity_res = entity_data.data;
entity_res.status = value.status;
shake_profiles.push(entity_res);
})
.catch(err => console.log('err fetching entity info: ', err));
break;
}
// if neither case do nothing
default: break;
}
});
dispatch({
type: 'FETCH_CONNECTIONS',
payload: { shake_profiles, connected_profiles },
});
});
};
}
Sample Reducer
///////////// Sample reducer /////////////
const initialState = {
fetched: false,
error: null,
connections: [],
sortType: 'first_name',
filterType: 'ALL',
shake_identities: [],
network_identities: [],
};
const connectionsReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_CONNECTIONS':
console.log('[connections REDUCER] shake_profiles: ', action.payload.shake_profiles);
console.log('[connections REDUCER] connected_profiles: ', action.payload.connected_profiles);
return { ...state,
fetched: true,
shake_identities: action.payload.shake_profiles,
network_identities: action.payload.connected_profiles,
};
default:
return state;
}
};
export default connectionsReducer;
Sample Store
///////////// Sample Store /////////////
import { applyMiddleware, createStore, compose } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import reducers from './reducers';
const middleware = applyMiddleware(promise(), thunk);
// Redux Dev Tools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(middleware));
export default store;
Sample Component - see if the API is done fetching the array, then display the length of the array
///////////// Sample Component /////////////
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import CSSModules from 'react-css-modules';
import * as ConnectionActions from 'actions/connections';
import styles from './styles.scss';
function mapStateToProps(state) {
return {
network_identities: state.connections.network_identities,
loadedConnections: state.connections.fetched,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, ConnectionActions), dispatch),
};
}
class Counter extends Component {
componentWillMount() {
const { network_identities, actions } = this.props;
if (!network_identities.length) {
console.log('||| fetching Connections');
actions.fetchConnections();
}
}
render() {
let { network_identities, loadedConnections} = this.props;
console.log('[Counter] network_identities[0]: ', network_identities[0]);
console.log('[Counter] network_identities: ', network_identities);
console.log('[Counter] loadingConnections: ', loadingConnections);
return (
<div>
<Link to="/network">
<div>
<span>Connections</span>
{ !loadedConnections ? (
<span><i className="fa fa-refresh fa-spin" /></span>
) : (
<span>{network_identities.length}</span>
) }
</div>
</Link>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CSSModules(Counter, styles));
I suspect I am either mutating the state in my reducer, or I am misusing Thunk.
The problem in the code is that connections.data.forEach((value) => {..}) will send out a bunch of fetches, and then immediately return without waiting for the result arrays to be populated. A 'FETCH_CONNECTIONS' action is dispatched with empty arrays, and all connected components will rerender with the empty results.
What makes it tricky though is that the array objects that you put in the store will get pushed to once the fetches finish, so when you inspect the store it will seem populated correctly.
Not using any mutations will prevent the accidental population of the store, but won't solve the fact that dispatch is fired before the results are in. To do that, you could either create actions to add single results and dispatch those in the axios.get().then parts, or you could create a list of promises and wait for all of them to resolve with Promise.all().
Here's what the latter solution could look like.
axios.get(`${url}/connection`, params)
.then((connections) => {
const connectionPromises = connections.data.map((value) => {
switch (value.status) {
case 'APPROVED': case 'UNAPPROVED':
return axios.get(`${url}/entity/${value.entity_id_other}`, params)
.then((entity_data) => {
return {connected_profile: {...entity_data.data, status: value.status}};
});
case 'CONNECTED':
return axios.get(`${url}/entity/${value.entity_id_other}`, params)
.then((entity_data) => {
return {shake_profile: {...entity_data.data, status: value.status}};
})
// if neither case do nothing
default:
return {};
}
});
Promise.all(connectionPromises)
.then((connections) => {
const connected_profiles =
connections.filter((c) => c.connected_profile).map((r) => r.connected_profile);
const shake_profiles =
connections.filter((c) => c.shake_profile).map((r) => r.shake_profile);
dispatch({
type: 'FETCH_CONNECTIONS',
payload: { shake_profiles, connected_profiles },
});
}).catch(err => console.log('err fetching entity info: ', err));
});
You'll probably want to use some more appropriate names though, and if you use lodash, you can make it a bit prettier.
The issue here is that you are making an async operation within a componentWillMount. When this lifecycle method is called,it does not block the render method from being called. That is, it does not wait until there is a response from its operations. So, rather move this async action to componentDidMount.