I am new to ReactJS. I am trying to use ReactJS and redux together.
Can anybody explain how to connect the two?
Also, in redux reducer, we usually set the intialState to some value. How can I set the value which is already present in react component's state to the initialState of reducer?
Below is initialization done in my reducer,
const initialState = {
pricing:{
total:0
},
appliedPromo:false
}
Below is my state in react component,
state = {
pricing: {},
itemDetails: {},
error: false,
seeDetails:false,
showPromoCode:false,
promocodeApplied:false,
user_discount:""
}
I will update the state using axios,
componentDidMount() {
axios
.get("https://purchsum-fb152.firebaseio.com/data.json")
.then(response => {
this.setState({ pricing: response.data.Pricing, itemDetails: response.data.itemDetails });
})
.catch(error => {
this.setState({ error: true });
});
}
then I will connect state to props,
const mapStateToProps = (state) => {
return{
total: state.pricing.total,
promoApplied: state.appliedPromo
}
}
const mapDispatchToProp = dispatch =>{
return{
onDiscount:()=>dispatch({type:"DISCOUNT",value:10,percentage:100})
}
}
export default connect(mapStateToProps,mapDispatchToProp)(PurchaseOrder);
The application works only if i set initialState as following,
const initialState = {
pricing:{
total:108.03
},
appliedPromo:false
}
I don't want to set it (no hard code). Instead I want reducer to take state value which is updated in the component.
I don't want to set it (no hard code). Instead I want reducer to take
state value which is updated in the component.
That's not the redux pattern unfortunately. You must set the intial state for the reducer to load up - I would not suggest that this can or should be done dynamically.
However, can you not create an action which will set the state to what you want when your Component mounts?
Related
I am using redux in a project and I want to make a useSelector that would check to see if the values in the redux state are the default values if not it will do a request to the database and update the the state I feel like it is quite complicated though and I am having a hard time getting my head around how I need to do this.
I need to do this because sometimes the correct state is not loaded in the state I am considering just doing a check every time I use useSelector to check if the values are the default values then fetch from the database but I would much prefer to write it a way that would allow to be handled within the redux selector but I can't really grasp I how I need to do it.
const info = useSelector(getInfo)
Ideally I would like the info to be handled when I fetch here
import { SET_USER_DETAILS } from "../constants/userConstants";
const intialState = {
user: { },
};
const userReducer = (state = intialState, action: any) => {
switch (action.type) {
case SET_USER_DETAILS:
return { ...state, user: action.payload };
default:
return state;
}
};
here is what my current reducer looks like what would be the best way to do this as I am finding it a little bit difficult to follow the documentation on the redux website.
You can use redux-thunk. https://redux.js.org/usage/writing-logic-thunks
then your thunk could look something like that:
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
const currentState = getState() as typeof initialState;
// check if state is default state
if (JSON.stringify(initialState) === JSON.stringify(currentState)) {
fetch(url).then(data => {
dispatch({type: SET_USER_DETAILS, payload: data})
})
}
}
You need first to fetch data in react component:
const MyComponent = () => {
// while fetch is fetching, data will be default state,
// and when fetch is done, that component will automatically
// rerender with new data
const data = useSelector(getInfo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(thunkFunction)
},[])
return <code>{JSON.stringify(data, null, 2)}</code>
}
I did not test it so may require some changes
but in general concept is like this
My Redux Store is correctly being updated which can be seen using React Native Debugger. However, the props inside my component are not updating and are undefined.
In my component below you can see I have correctly mapped to the "sessionModerator" reducer. I have verified this and can see the prop when consoling this.props.
Component:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.sharedReducers.sessionModerator
};
};
My reducer is added as seen below:
Reducers Index file:
import { reducer as sessionModerator } from './session/reducers/session-moderator';
export const reducers = combineReducers({
sessionModerator: sessionModerator,
});
Actions File:
import Types from '../../../types';
export const start = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_START,
payload: true
};
};
export const stop = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_STOP,
payload: false
};
};
Reducers File:
import Types from '../../../types';
export const reducer = (state = false, action) => {
switch (action.type) {
case Types.TYPES_SESSION_MODERATOR_START:
return action.payload;
case Types.TYPES_SESSION_MODERATOR_STOP:
return action.payload;
default:
return state;
}
};
In the below image you can see that the store is updated as the value for sessionModerator is set to "true", but the console of the actual props during the operation is undefined.
What I have tried:
I have tried various things mostly revolving around the structure of my state, for example, I tried adding the boolean inside an actual object and updating the value as an object property but that didn't seem to work. I feel like I am not updating the boolean correctly but haven't been able to figure it out.
Any help would be greatly appreciated. Thank you.
sessionModerator is in screenReducers in the debugger not in sharedReducers as in your mapStateToProps.
Try this one:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.screenReducers.sessionModerator
};
};
I'm trying currently to pass the app.state contained to the Redux store in a React Component.
So far, this problem is still a deep mystery...
------> HERE THE GITHUB REPOSITORY OF MY CODE <------
Hope it will help to figure out what is wrong.
Abstract :
My problem is basically about mapStateToProps, is about link a Component to the state store, AFAIK the rest work very fine, but Something seems shortcut my this.props in React's Component, because either I use connect() or delete the mapStateToProps method, my Component stil display the initial state ..!
Redux resists me like an end-level's boss...
STATE OF PLAY
The provider with a store of react-redux: OK
Connect function pass to the props: OK
mapDispatchToProps works fine! So why the state fails to update the props since the connection seems well established?
I know my action is well mapped since when I delete the mapDispatch in the connect composition, the component then fails to trigger the corresponding action.
When console.log, the mapState receive effectively the store update but the Component stay blocked on initial state (tested with a "checkState" button on the component which returns the "store.getState().propertyTargeted"
HINTS :
when I delete the mapStateToProps in connect, my React.Component continue to receive the initialState,
so maybe there is an another source that overwrites my mapStateToProps, I seek for it currently
my this.props.state variable is called in the Component's constructor, maybe the constructor doesn't receive the store.updateState or something like that ? Another track to follow.
Here my combineReducer.js :
import { combineReducers } from "redux";
import {post} from "./status"
import {entry}from "./updateState";
// only one reducer active
const appReducer = combineReducers({
entry,
post
})
export default appReducer
Here my container.js :
const mapStateToProps = (state) => {
return { word: state.entry.word }
}
const mapDispatchToProps = {
postFile: postFileAction
}
const PostFileContainer = connect(mapStateToProps, mapDispatchToProps)(Component) ;
My postFile.js :
export const postFile = (word, base64Data) => dispatch => {
console.log("postFile httpRequest reached")
dispatch({
type: 'POST_WORD',
status: request
});
Axios.post("http://localhost:7500/api/files", {
"word": word,
"data": base64Data
}, {
"Content-Type": "multipart/form-data"
})
.then(res =>
dispatch({
type: 'POST_WORD',
status: success,
res
}))
.catch(err => {
dispatch({
type: 'POST_WORD',
status: error,
err
})
});
}
Here in my store.initialState :
initial state: {
"post": {},
"entry": {
"word": "initialWord"
}
}
the UPDATE_STATE_POSTWORD is provide by an other React component therefore dispatched to the store before that the bugging component trigger it own action with a updated word's entry.
Here my UPDATE_STATE_POSTWORD action snippet :
export const updateWord = word => {
return {
type: UPDATE_STATE_POSTWORD,
word
};
}
/// reducers.js part ///
postReducer.js :
export const post = (state ={}, action) => {
console.log("postStatus reached - reducer")
switch (action.status) {
case request:
console.log("Request start")
return state
case success:
switch (action.type) {
case POST_FILE:
console.log("request succeed: ", action.res)
var _id = action.res._id
// var word= action.res.word
return (Object.assign({}, state, {
_id
}))
case POST_WORD:
console.log("request succeed: ", action.res)
return (Object.assign({}, state, {
_id: ""
}))
default :
console.log(`default state on success case in
postStatusReducer`)
return state
}
case error:
console.log("request error: ", action.err)
return state
default:
return state
}
}
entryReducer.js :
const initialState = { word : "initialWord" }
export const updateStateReducer = (state= initialState, action) =>
{
switch (action.type) {
case UPDATE_STATE_POSTWORD:
var word = action.word
return (Object.assign({}, state, {
word
}))
default:
return state
}
}
Thanks
If you are using react-thunk, your action fn would receive dispatch and getState functions as arguments.
Running getState would give you actual state of the application. Recuired data would be passed to reducer and so on.
In your example RecordingAPI receives props that comes from redux only while initializing - in constructor.
You can fix your component by adding componentWillReceiveProps method
class RecordingAPI extends React.Component {
constructor(props) {
super(props);
...
this.state = {
word : this.props.word,
state: this.props
};
}
// new method that listens to props
componentWillReceiveProps (props) {
this.setState({
word: this.props.word,
state: this.props
});
}
checkState(e){
e.persist();
e.preventDefault();
e.stopPropagation();
console.dir(this.state.word)
console.dir(this.state.state)
}
render() {
...
return (
<div>
<button onClick={(e) => this.checkState(e)}> CheckState </button>
</div>
);
}
}
My current work-around is to import the store directly in my React Component then subscribe to the changes as it :
import {store} from "../App"
store.subscribe(() => {
// When state will be updated
// we will update local component state and force component to rerender
// with new data.
this.setState({
word: store.getState().entry.word // new entry.words at each update in the statge of the React.Component
});
});
ANSWER :
Assigning the store.state value to the Component's state constructor, the Component failed to update the state. So, referring to the store.state using this.props outside any assignment to the Component.state.property works like a charm*.
The trap is that storing a props in the props.constructor.state of the children works when you work only with React.js but this mechanism doesn't works for React-Redux then you have to stay the props outside any assignment in the props.constructor.state
I have a simple redux action and reducer that changes state without making any sort of web service request:
// Action
export const setMyState = (value) => {
return {
type: 'SET_STATE',
payload: value
};
};
// Reducer
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_STATE':
return { myState: action.payload }
}
}
When I set the state via the action in my react component and immediately call that state from the redux prop binding, I do not get the current state:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
this.props.setMyState('value');
console.log(this.props.myState);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
I understand that render will be called again when the redux state and binded props change, but what I'm trying to do is fire off another Redux action based on the resulting state. My options appear to be:
Figure out how to latest Redux state in React immediately after state change
Fire off a redux action in the reducer instead, which sounds like an anti-pattern.
Set some sort of state in my component or redux that the action should be fired on the next render, which seems clunky.
config your reducer like:
switch (action.type) {
case 'SET_STATE':
return {
...state,
loading: true,
};
case 'SET_STATE_SUCCESS':
return {
...state,
loading: false,
payload: value
};
}
and then in your component listen to
this.props.myState.loading
then trigger your action if (loading) and so on!
My solution is to use event loop handles the update, not the call stack. In particular for your case you the implementation would include setTimeout(() => {}); as the following:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
setTimeout(() => { this.props.setMyState('value'); },0);
setTimeout(() => { console.log(this.props.myState); },0);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
What are you using this Redux state for?
If you are trying to access the data you had just set in the Redux state or if it's data that's already sitting in the Redux state you could dispatch both calls at the same time.
If you need to make the call because there is some work done to the data while setting it in the Redux state (I recommend doing this work in the action not the reducer) then componentWillReceiveProps(nextProps) will do what you need. You can compare the current props with the next props and trigger another update to the Redux state.
The best way to do this would probably be to use a Redux middleware.
This might be a question of best practices but I'd appreciate an explanation on why this doesn't work. I'm using Typescript + Redux + Thunk and trying to call actions like this:
export const requestUserDashboards = createAction<DashboardModel>(Type.REQUEST_USER_DASHBOARDS);
Dispatch in the fetch:
export const fetchDashboards = () => {
return async (dispatch: Dispatch, getState: any) => {
try {
dispatch(requestUserDashboards({
currentDashboard: getState.currentDashboard,
dashboards: getState.dashboards,
hasDashboards: false,
error: getState.error
}))
...
}
})
}
Here's the corresponding reducer:
export const dashboardReducer = handleActions<RootState.DashboardState, DashboardModel>(
{
[DashboardActions.Type.REQUEST_USER_DASHBOARDS]: (state = initialState, action): RootState.DashboardState => ({
currentDashboard: action.payload!.currentDashboard,
dashboards: action.payload!.dashboards,
hasDashboards: action.payload!.hasDashboards,
error: action.payload!.error
})
},
initialState
);
dispatch is working, however, getState doesn't correctly collect the current store state. I'm testing this by doing the following in the component receiving the updated store:
componentWillReceiveProps(nextProps: Login.Props) {
console.log(nextProps.defaultAccounts.defaultAccount);
}
Calling this in the component using:
this.props.defaultAccountActions.fetchUserDefaultAccount();
The action is working as the values from the fetch are being captured.
However, where I am using the getState.xxxx, these values are returning as undefined:
index.tsx:84 Uncaught TypeError: Cannot read property 'defaultAccount' of undefined
The initialState from my reducer is working. I can see this from doing the console.log(this.props.defaultAccounts.defaultAccount) from the componentWillMount() function.
I'm not sure what else I can provide. I think I'm actually just fundamentally misunderstanding how actions/reducers manage the store.
Questions
I am trying to get the current store values by using the getState.xxxx in the dispatch. Is this the correct way to do this?
isn't getState a function in that place? So you would need to do something
const state = getState();
and then use state inside dispatch
found in documentation, yeah it is a function at that place so you should firstly invoke a function to get state and then use it (e.g. from documentation below)
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
If you are using mapstatetoprops in your component you can use that to get the values from store. mapStateToProps first argument is actually the Redux state. It is practically an abstracted getState().
const mapStateToProps = function(state, ownProps) {
// state is equivalent to store.getState()
// you then get the slice of data you need from the redux store
// and pass it as props to your component
return {
someData: state.someData
}
}