I try to connect a function that is not a component to mapStateToProps using React Redux connect().
The goal is to access redux state outside of the component.
But once i try to run the function connectedJob() nothing happens.
const mapStateToProps = state => {
return {
user: state.user
};
};
const job = ({user}) => console.log(user.id);
const connectedJob = connect(mapStateToProps)(job);
connectedJob(); //undefined
Related
Warning: You have opted-out of Automatic Static Optimization due to
getInitialProps in pages/_app. This does not opt-out pages with
getStaticProps
I tried different options, but I can’t achieve static page generation, even if I take out the functionality I need from getInitialProps from_app, then wrapping it in withRedux, I still get it in the end. I tried with this - https://github.com/kirill-konshin/next-redux-wrapper - but could not get the result, I assume that this is because of the redux-saga and the whole application will use getInitialProps
/store.js
const ReduxStore = (initialState /*, options */) => {
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware)
// other store enhancers if any
);
const store = createStore(
rootReducer,
initialState,
enhancer
);
store.runSaga = () => {
// Avoid running twice
if (store.saga) return;
store.saga = sagaMiddleware.run(saga);
};
store.stopSaga = async () => {
// Avoid running twice
if (!store.saga) return;
store.dispatch(END);
await store.saga.done;
store.saga = null;
// log('Stop Sagas');
};
store.execSagaTasks = async (ctx, tasks) => {
// run saga
await store.runSaga();
// dispatch saga tasks
tasks(store.dispatch);
// Stop running and wait for the tasks to be done
await store.stopSaga();
// Re-run on client side
if (!ctx.isServer) {
store.runSaga();
}
};
store.runSaga();
return store;
};
export default ReduxStore;
//_app.js
import { Provider } from 'react-redux';
import withRedux from 'next-redux-wrapper';
import App from 'next/app';
class MyApp extends App {
render() {
const {Component, pageProps, store} = this.props;
return <Provider store={store}>
<Component {...pageProps}/>
</Provider>;
}
}
export default withRedux(makeStore)(MyApp);
Has anyone experienced this or have any ideas? I will be grateful for any help
Combining reducers
export default (injectedReducers = {}) => {
return combineReducers({
...injectedReducers,
memoizedStamps: memoizedStampsReducer, // <-- need to write here
});
};
Writing an action
const mapDispatch = (dispatch) => ({
addStamp: (payload) =>
dispatch({
type: ADD_STAMP,
payload,
}),
});
Writing the reducer
export const initialState = [];
const memoizedStampsReducer = produce((draft, action) => {
const { type, payload } = action;
switch (type) {
case ADD_STAMP:
draft.push(payload);
}
}, initialState);
export default memoizedStampsReducer;
Using in a react hook
const useMemoizedStamps = () => {
const [memStamps, dispatch] = useImmerReducer(reducer, initialState);
const { addStamp } = mapDispatch(dispatch);
useEffect(() => {
addStamp({ body: 'body', coords: 'coords' });
}, []);
console.log(memStamps); // <-- gives [{ body: 'body', coords: 'coords' }] all good here
return null;
};
export default useMemoizedStamps;
But it gets never saved into injected reducer "memoizedStamps". The array is always empty. It works perfectly will with connect(null, mapDispatchToProps), but can't use connect() in my custom hook.
What do I do wrong? What is the answer here?
--- UPD 1 ---
#phry, like so?
const useMemoizedStamps = (response = null, error = null) => {
const dispatch = useDispatch();
const [memStamps] = useImmerReducer(reducer, initialState);
const { addStamp } = mapDispatch(dispatch);
useEffect(() => {
addStamp({ body: 'body', coords: 'coords' });
}, []);
console.log(memStamps);
return null;
};
And now if I need them to be local, I need to use immer's dispatcher? Any way to merge these two dispatchers? P.S. this dispatcher really saved it to global state.
Rereading this, I think you just have a misconception.
Stuff is never "saved into a reducer". A reducer only manages how state changes.
In Redux, it would be "saved into the store", but for that you would have to actually use a store. useImmerReducer has nothing to do with Redux though - it is just a version of useReducer, which like useState just manages isolated component-local state with a reducer. This state will not be shared with other components.
If you want to use Redux (and use it with immer), please look into the official Redux Toolkit, which already comes with immer integrated. It is taught by the official Redux tutorial.
I'd managed to get some of my earlier functions state in devtools as below:
Reducers function in DevTools
But when I tried to query some of the events in my interactions, the functions state werent able to display it. Below are my codes and settings, basically the flow is interactions > actions > reducers
interaction code:
export const loadAllOrders = async (exchange, dispatch) => {
// Fetch cancelled orders with the "Cancel" event stream
const fromBlock = 0;
const toBlock = "latest";
const cancelFilter = exchange.filters.CancelOrder();
const cancelStream = await exchange.queryFilter(cancelFilter, fromBlock, toBlock);
console.log(cancelStream)
// Format cancelled orders
const cancelledOrders = cancelStream.map((event) => event.args);
// Add cancelled orders to the redux store
dispatch(cancelledOrdersLoaded(cancelledOrders));
}
from my actions:
export const cancelledOrdersLoaded = (cancelledOrders) => {
return {
type: 'CANCELLED_ORDERS_LOADED',
payload:cancelledOrders
}
}
from my reducers:
export const exchange = (state = initialState, action) => {
switch (action.type) {
case 'EXCHANGE_LOADED':
return { ...state, loaded:true, contract: action.payload }
case 'CANCELLED_ORDERS_LOADED':
return { ...state, cancelledOrders: action.payload }
default:
return state
}
my configureStore
// For redux dev tools
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk),devTools)
)
Thanks in advance
I haven't worked with redux for quite some time now, but from a quick look at some of my older repos, it seems like you didn't set up your store correctly.
This is what I have there,
import { applyMiddleware, createStore, compose, combineReducers } from "redux"
import thunk from "redux-thunk"
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const rootReducer = combineReducers({
reducers...
})
export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))
In a react-native, redux, firebase project, I have a drawer component that subscribes to an onSnapshot listener when the component mounts, and on will unmount, it calls the snapshot reference. this component looks like this:
import { onAccountChange } from '../actions/Agenda';
import {dispatch} from 'redux';
class DrawerContentComponent extends React.Component {
constructor (props) {
super(props);
}
componentDidMount(){
this.unsubscribeAccount = firebase.firestore().collection('users').doc(this.props.authUser.uid).onSnapshot((doc) => {
dispatch({type: types.LOAD_ACCOUNT, payload: doc.data()})
});
}
componentWillUnmount() {
this.unsubscribeAccount();
}
< ...rest of component... >
EDIT:
const mapStateToProps = ({ account, auth, inbox, agenda }) => {
const { role, profileImg, legalName, username, rating, phoneNumber } = account;
const { conversations } = inbox;
const { authUser } = auth;
const { events } = agenda;
return {
role,
profileImg,
legalName,
username,
rating,
phoneNumber,
authUser,
conversations,
events
};
};
const mapDispatchToProps = { logoutUser, onProfileChange, onAccountChange, getConversations, getAgenda };
export default connect(mapStateToProps, mapDispatchToProps)(DrawerContentComponent);
}
Edit: onAccountChange():
export const onAccountChange = (uid) => {
return (dispatch) => {
firebase.firestore().collection('users').doc(uid).onSnapshot((doc) => {
dispatch({ type: types.LOAD_ACCOUNT, payload: doc.data() });
});
};
};
The above functions as necessary, because I couldn't manage to unsubscribe from the action, which previously was placed in an external directory for actions.
Problem: I want to be able to implement this by somehow using the function thats already created in the actions file ( getAgenda()) without having to rewrite the code in the component, because im currently doing that just to have the ability to unsubscribe from the listener on unmount, only way I thought of to make it work.
ideally, id like to do something like this:
componentDidMount() {
this.unsubscribeAgenda = this.props.getAgenda();
}
componentWillUnmount() {
this.unsubscribeAgenda();
}
But the above results in:
TypeError: 'dispatch is not a function' if I take out the dispatch import, the error is ReferenceError: Cant find variable: dispatch, I obviously need to dispatch changes for a onSnapshot listener
What are some strategies to handle this?
You can't import dispatch directly from redux.
You need to either use react-redux's connect() function to wrap your action creators with dispatch or get dispatch directly from it.
If you are using a functional component, you could use useDispatch to get access to it.
If you don't want to use one of the normal react-redux options, you can export dispatch from your store, and then import it from where you created your store.
export const dispatch = store.dispatch
If most of your logic for the firestore is in an redux thunk action (or similar with asynchronous capabilities), use connect to get the action wrapped in dispatch and run it as you have in your ideal at the end. Whatever you return from a thunk action is returned from the call as well, so you should be able to set it up to return the unsubscribe function.
connect({},{onAccountChange})(DrawerContentComponent)
Then you can dispatch onAccountChange action creator using:
this.props.onAccountChange()
Edit:
Modify your onAccountChange function to this so that your thunk returns your unsubscibe function.
export const onAccountChange = (uid) => {
return (dispatch) => {
return firebase
.firestore()
.collection('users')
.doc(uid)
.onSnapshot((doc) => {
dispatch({ type: types.LOAD_ACCOUNT, payload: doc.data() });
});
};
};
Then you just need to add onAccountChange to the mapDispatch to props and use this in your componentDidMount method:
this.unsubscribeAccount = this.props.onAccountChange();
For making components to be attached to store for both dispatch actions or mapping props, it is used with connect(mapStateToProps, mapDispatchToProps)(Component). in your case, there is no props passed to component so I'll just send null for mapStateToProps
(assuming you used Provider at some parent component REDUX. I cant understand how to connect a component defined as a class extending React.Component in order to read the store)
import { connect } from 'react-redux';
class DrawerContentComponent extends React.Component {
...rest code...
componentDidMount() {
this.unsubscribeAgenda = this.props.getAgenda();
}
componentWillUnmount() {
this.unsubscribeAgenda();
}
}
export default connect(null, { getAgenda })(DrawerContentComponent)
I am using axios.get in my useeffect and then I am passing the data from response to the dispatcher. It retrieves data and dispatches and then I get the state to show data an console it but data shows infinite loop. Here is my useEffect, local state, mapStateToProps and mapDispatchToProps.
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
const mapStateToProps = state => ({
showUsers: state.getUsers.users
})
const mapDispatchToProps = dispatch => ({
__storeUsers: (data) => dispatch({type: types.STORE_USERS, payload: data}),
})
This is my reducer for users
import * as types from "./types";
const initialState = {
users: []
}
const usersState = (state = initialState, action) => {
switch (action.type) {
case types.STORE_USERS:
return {
...state,
users: action.payload
}
default:
return state
}
}
export default usersState
This is for practice purpose. i am not using actionCreators right now. After this I will move the axios call to the action creator. The data that I get from above goes in loop in console. Please help.
Also if I create action creator for this, that also goes in loop. Action creator is like below:
export const UserActions = () => async (dispatch) => {
const response = await axios.get('https://jsonplaceholder.typicode.com/users')
if (response.data) {
// console.log(response.data)
dispatch({
type: types.STORE_USERS,
payload: response.data
})
} else {
// console.log("no data")
}
return response
}
And then I use it like below
const mapDispatchToProps = dispatch => ({
__storeUsers: () => dispatch(UserActions())
})
Both methods are firing loop in console.
Notice that your useEffect here is where the infinite loop occurs:
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
The useEffect has been told that users is one of its dependencies and that it should re-run when this variable changes. The useEffect then changes the value of users via setUsers and it sees this update so runs again.
It looks like you're only depending on users for this console.log. Consider taking it out of the dependency list.