Redux - initializing a prop in the store with function - reactjs

Say had a reducer for a certain state
... function thisFunction (
state = new state,
action
)
{
switch(type) {
case INIT_STATE:
return {
...state,
...action.state
}
default:
return {
certainProp: setCertainProp(state.certainProp, action)
}
and my reducer for it
function setCertainProp(state = {}, action) {
const { type } = action
switch (type) {
case SET_CERTAIN_PROP:
return action.certainProp
default:
return state
and now my action
function setCertainPropAction(prop){
return {
type: SET_CERTAIN_PROP
action: prop
}
}
now inside my container in a componentDidMount or useEffect I want certainProp's value to
come from a fetch call
useEffect(() => {
fetchCertainProp().then(response => setCertainPropAction(response)
}, [])
my question is, is it possible to completely remove the useEffect here and call the fetchCertainProp() from inside the initialization of certainProp within the reducer?
I have access to redux-thunks but I haven't seen an example of how this would be possible. I don't want to add an unnecessary useEffect if I don't need too.

Related

React-Redux , issues reading an object fetched through redux thunk

I have a redux action / reducer that looks like the following.
Action:
export function loadServerInfo() {
return (dispatch) => axios.get(`${config.SERVER}/redis/server/info`).then(res => {
if (res.status == 200) {
dispatch(fetchServerInfo(res.data))
}
}).catch(err => {
})
}
export function fetchServerInfo(payload) {
return {
type: GET_SERVER_INFO,
payload
}
}
Reducer:
const defaultState = {
decodedRedisKey: {},
keyDecoded: false,
serverInfo: {}
}
const redisReducer = (state = defaultState, action: Action) => {
switch (action.type) {
case GET_REDIS_KEY_INFO: {
return {
...state,
decodedRedisKey: action.payload
}
}
case REDIS_KEY_DECODED: {
return {
...state,
keyDecoded: action.payload
}
}
case GET_SERVER_INFO: {
console.log(action.payload) //this is fired and logs the proper data, which is an object
return {
...state,
serverInfo: action.payload
}
}
default:
return {
...state
};
}
}
export default redisReducer;
Then I have a component connected and mapped to redux. Those are the connection parameters
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadServerInfo: async () => {
dispatch(loadServerInfo());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainer);
And after that, I try to call the fetch, and get the data.
Problem is that the format of the object is as follows:
serverInfo: {
Server : {
uptime_in_days: "100",
version: "1.0.0"
}
}
My prop is firing on useEffect
React.useEffect(() => {
getUsersToken();
props.loadServerInfo();
console.log(process.env.REACT_APP_ENV)
}, []);
If i put it in a useEffect, first it logs undefined and afterward it loads
React.useEffect(() => {
console.log("server info")
console.log(props.redisReducer.serverInfo)
console.log(props.redisReducer.serverInfo.Server)
// console.log(props.redisReducer.serverInfo.Server.uptime_in_days) , if i uncomment this it crashes
}, [props.redisReducer.serverInfo])
So im having issues rendering the uptime_in_days value
I have tried doing this
{props.redisReducer.serverInfo != undefined && !displayServerInfo != undefined ?
<div className="basic-server-info-data">
<p><img src={redisLogo} /></p>
{/* <p>Connected Clients: <i>{serverInfo.Clients.connected_clients} </i></p> */}
{/* <p>Memory usage: <Progress type="circle" percent={memoryUsageStats} width={50} /> </p> */}
<p>Tokens (displayed): <i>{usersToken.length}</i></p>
<p>Uptime: <i>{props.redisReducer.serverInfo.Server.uptime_in_days} days</i></p>
</div>
:
null
}
It keeps crashing in the Uptime line, even tho im doing a check if its not undefined
Cannot read property 'uptime_in_days' of undefined
I tried changing the render condition to
props.redisReducer.serverInfo != undefined && !displayServerInfo != undefined && props.redisReducer.serverInfo.Server.uptime_in_days != undefined
But nothing changes.
How can I render that value?
EDIT: I have noticed this error
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
in my useEffect
Issue
The issue is that all your null checks start with the always defined state, props.redisReducer.serverInfo
const defaultState = {
decodedRedisKey: {},
keyDecoded: false,
serverInfo: {} // <-- defined!
}
state.serverInfo is always a defined object, so console.log(props.redisReducer.serverInfo) and console.log(props.redisReducer.serverInfo.Server) will always log, and the condition props.redisReducer.serverInfo != undefined will always be true.
You neglect to do a null check on props.redisReducer.serverInfo.Server before accessing the uptime value
props.redisReducer.serverInfo.Server.uptime_in_days
I'm guessing your UI is blowing up on the initial render before state is populated.
Solutions
Use Optional Chaining to handle the null check on Server being possibly undefined still.
props.redisReducer.serverInfo.Server?.uptime_in_days
Use conventional null checks
props.redisReducer.serverInfo.Server &&
props.redisReducer.serverInfo.Server.uptime_in_days

Redux , state.concat is not a function at rootReducer. And being forced to reRender an element for it to see the state change

So I have this sidebar component where I load my store and my dispatcher
//select
const mapStateToProps = state => {
return { renderedEl: state.renderedEl }
}
function mapDispatchToProps(dispatch) {
return{
renderLayoutElement: element => dispatch(renderLayoutElement(element))
}
}
Then inside the same component this Is how I trigger the dispatcher
renderEl = (el) => {
var elementName = el.target.getAttribute('id');
var renderedElements = this.props.renderedEl; //this is data from the store
for (let key in renderedElements) {
if (key == elementName) {
renderedElements[key] = true
}
}
this.props.renderLayoutElement({renderedElements});
}
Then as I understand it gets sent to the reducer
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
const initialState = {
renderedEl: {
heimdall: false,
skadi: false,
mercator: false
}
}
function rootReducer(state = initialState, action){
if(action.type === RENDER_LAYOUT_ELEMENT){
return Object.assign({},state,{
renderedEl: state.renderedEl.concat(action.payload)
})
}
return state
}
export default rootReducer;
This is its action
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
export function renderLayoutElement(payload) {
return { type: RENDER_LAYOUT_ELEMENT, payload }
};
Now the thing is. Im receiving a
state.renderedEl.concat is not a function at rootreducer / at dispatch
I dont understand why does that happen.
Becuase, actually the store gets updated as I can see, but the console returns that error. And I have to reload the render that uses the props of that store (with an onhover) in order to be able to see the changes. It doesnt happen automatically as it would happen with a state
if(action.type === RENDER_LAYOUT_ELEMENT){
return { ...state, renderedEl: { ...state.renderedEl, ...action.payload } };
}
Duplicate from comments maybe it can be helpful to someone else :)

React Native ShouldComponentUpdate not firing between Redux Props Changes

In optimizing my component for performance, I noticed that sequential shouldComponentUpdate calls were seemingly missing the prop change from my redux store. An example being:
In props:
uiBehavior: {
shouldShow: false,
}
Redux Action:
fireActionToShow()
Redux Reducer:
case ActionType.UPDATE_UI_BEHAVIOR: return {...state, shouldShow: true}
Then I'm seeing:
shouldComponentUpdate(nextProps) {
// Would expect to at some point see:
this.props.shouldShow === false
nextProps === true
// Instead, only seeing
this.props.shouldShow === false
nextProps === false
// then
this.props.shouldShow === true
nextProps === true
}
** Note that this clearly isn't the code, just an example
It seems to me that a prop change isn't causing a rerender attempt, or am I missing something?
Thanks.
Expanding for clarity, here is some of the real code:
*** the event in the Action updates the uiBehavior prop on the redux store.
componentDidUpdate(prevProps, prevState) {
const { uiBehavior } = this.props;
if (!_.isEqual(uiBehavior.lockAttributes, prevProps.uiBehavior.lockAttributes)) {
console.log('Lock has changed.'); // This never gets called
}
}
const mapStateToProps = function(state){
return {
uiBehavior: state.uiBehavior,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SlotMachine)
*** UPDATE
JS Call:
this.props.setGeneralUiKeyValue('lockAttributesInCart', !lockAttributes);
Action:
export const setGeneralUiKeyValue = (key, value) => { return { type: GENERAL_UI_UPDATE, key, value }}
Reducer:
export const uiBehavior = (state = {}, action) => {
let newUiState = {...defaultState, ...state}
switch (action.type) {
case uiActionTypes.GENERAL_UI_UPDATE:
newUiState.general = newUiState.general || {};
newUiState.general[action.key] = action.value;
return newUiState
default:
return newUiState;
}
return newUiState

Slice of Redux state returning undefined, initial state not working in mapStateToProps

I am trying to simply sort on the redux store data in mapStateToProps, similar to how it is being done in Dan Abramov's Egghead.io video: https://egghead.io/lessons/javascript-redux-colocating-selectors-with-reducers
My problem is, initially the state is returning undefined (as it is fetched asynchronously), so what would be the best way to deal with this? Current code is as follows (_ is the ramda library):
const diff = (a, b) => {
if (a.name < b.name) {
return -1
}
if (a.name > b.name) {
return 1
}
return 0
}
const mapStateToProps = (state) => {
return {
transactions: _.sort(diff, state.transactions.all),
expenditure: state.expenditure.all,
income: state.income.all
}
}
I thought that transactions.all should initially be an empty array (which would mean the code would work) because of the initial state set in the reducer:
const INITIAL_STATE = { transactions: { all: [] }, transaction: null }
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_TRANSACTION:
return { ...state, transaction: action.payload.data }
case FETCH_TRANSACTIONS:
return { ...state, all: action.payload.data }
case EDIT_TRANSACTION:
return { data: action.data }
case ADD_TRANSACTION:
return { data: action.data }
case DELETE_TRANSACTION:
return { ...state }
default:
return state
}
}
Thanks in advance.
As you said, it is fetched asynchronously. Perhaps when the component rendered, data isn't ready yet which resulted to an undefined object.
const SampleComponent = (props) => {
if(props.transaction === undefined)
return <Spinner /> // Loading state
else
// your implementation
}
You can further make the code cleaner as explained by Dan himself in the docs here: http://redux.js.org/docs/advanced/AsyncActions.html
Managed to solve this, because in combine reducers, I had set transactions with the name transactions and then in the reducer, I essentially had the initial state set to transactions: { all: [] } }.
This was causing state.transactions.all to be undefined, as the correct state structure was actually state.transactions.transactions.all.
After updating the transactions reducer to:
const INITIAL_STATE = { all: [], transaction: null }
export default function (state = INITIAL_STATE, action) {
switch (action.type) {...
The initial empty transactions array prior to the promise returning meant the sort no longer causes an error, and is then correctly sorted on load.

React redux - issues adding multiple items to an array in state tree object

I am looking at redux and adding names to an array. The code below works (kind of!).
I have a few issues.
I know that it is advised to create a new state tree object each time the state is passed through the reducer, however I thought it should still work even if I change the state object passed in.
In my code below the console.log(store.getState()); works if I use var newArr = state.names.concat(action.name); but not if I use state.names.push(action.name);
If I add another store.dispatch(action) the code doesn't work.
store.dispatch({type: 'ADD_NAME',name: 'PhantomTwo'});
Can anyone explain why this is so?
Finally, do I need to return state again outside the switch statement?
Here is the code I currently have below.
const initialState = {
names: []
}
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
var newArr = state.names.concat(action.name);
return newArr;
default:
return state;
}
}
let store = createStore(namesApp);
store.dispatch({
type: 'ADD_NAME',
name: 'Phantom'
});
console.log(store.getState()); //returns `["Phantom"]`
This is the behavior of array object mutability
Since React highly cares about state change for re-rendering, so we need to take care of mutability.
The below snippet explains the array mutability.
let x = [];
let y = x;
console.log(x);
console.log(y);
y.push("First");
console.log(x);
console.log(y);
let z = [...x]; //creating new reference
console.log(z);
x.push("Second");
console.log(x); //updated
console.log(y); //updated
console.log(z); //not updated
So for better functionality your reducer will be like
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
return {
...state, //optional, necessary if state have other data than names
...{
names: [...state.names, action.name]
}
};
default:
return state;
}
}
[].concat returns a new array. But your state was { name: [] }. Inspite of returning newly build object with new names, the code above returned the new names array.
Vanilla solution
const initialState = { names: [] };
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
var newArr = state.names.concat(action.name);
return {
...state,
names: newArr
};
default:
return state;
}
}
immutability-helper
For this type of work I would use immutability-helper
import u from 'immutability-helper';
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
return u(state, {
names: {
$push: action.name
}
});
default:
return state;
}
}
learn how to use immutability-helper https://facebook.github.io/react/docs/update.html

Resources