I'm working on a React Native app, and I'm having some issues regarding conditional rendering based on the Redux state, which I fetch using useSelector.
I have tried with and without the useEffect below (so with returning the component directly instead of setting a state), but the variables returned from useSelector do not seem to change when the state is updated. Since the states are loading at first, I end up in the latest else if, and get stuck there. I have to then refresh the app to get the actual values and get to the desired screen
const ContainerA = ({ navigation }) => {
const {
loginSuccess,
loginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
const [toRender, setToRender] = useState(null);
useEffect(() => {
if (loginSuccess) {
if (loginSuccess.success === 1 && accountType === 3) {
console.log('[Container] case 1');
setToRender(<PageA navigation={navigation} />);
// return <PageA navigation={navigation} />;
// return;
} else if (
(loginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) || (loginSuccess.success === 0)
) {
console.log('[Container] case 2');
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (loginLoading || permissionsLoading) {
console.log('[Container] case 4');
setToRender(<LoadingPage />);
// return <LoadingPage />;
// return;
}
}, [
loginSucess,
loginLoading,
accountType,
navigation,
permissionSuccess,
permissionsLoading,
]);
return toRender;
};
export default ContainerA;
Redux reducer:
case 'loginInit':
return updateState(state, {
loginLoading: true,
loginSuccess: null,
loginFail: null,
});
case 'loginSuccess':
return updateState(state, {
loginLoading: false,
loginSuccess: action.success,
});
case 'loginFail':
return updateState(state, {
loginLoading: false,
loginFail: action.error,
});
case 'permissionsInit':
return updateState(state, {
permissionsLoading: true,
accountType: null,
permissionsSuccess: null,
permissionsFail: null,
});
case 'permissionsSuccess':
return updateState(state, {
permissionsLoading: false,
permissionsSuccess: action.success,
accountType: action.success.success
? action.success.success
: action.success.errors,
});
case 'permissionsFail':
return updateState(state, {
permissionsLoading: false,
permissionsFail: action.error,
});
updateState function:
export const updateState = (state, updatedProps) => ({
...state,
...updatedProps,
});
Seems like I was executing the functions that do the checks in a place where I shouldn't get any successful response. I fixed it by calling those functions in the Navigator where I was sure to have the token, since those calls required it (and which were not doing the actual API call without it). The code remaining in ContainerA is:
const {
firstLoginSuccess,
firstLoginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
if (firstLoginSuccess) {
if (firstLoginSuccess.success === 1 && accountType === 3) {
return <FirstTimeLoginPage navigation={navigation} />;
} else if (
(firstLoginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) ||
firstLoginSuccess.success === 0
) {
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (firstLoginLoading || permissionsLoading) {
console.log('[FirstTimeLoginContainer] case 4');
}
return <LoadingPage />;
Related
I have this code which seems to be executing each dispatch of the useReducer twice for some reason I don't get to resolve. I have tried to solve it by moving userReducer out of the component, but the results were the same. At this point I don't know how to approach the problem.
Another issue caused by the repetition of the code is that I create empty arrays [] to work with them but in the second repetition, it creates [''] over the empty array, which shouldn't happen.
Edit: Searching info, I disabled strict mode as well, but the problem persists.
I suggest to see the whole code here:
https://codesandbox.io/s/gallant-villani-zfwbpg?file=/src/App.js
(To trigger the problem, insert one number in the first input at the top, then press the button next to it, A red button will appear to restore previous values. When pressed, two consoles.log are displayed, this happens in every dispatch type.)
here is the useReducer:
function reducer(state, action) {
const arrayPueblosYexclus = ["pueblos_T1","pueblos_T2","pueblos_T3","exclusiones"];
switch (action.type) {
case "RESTORE":
console.log("restored");
return valorPrevio;
break;
case "FETCH_SUCCESS":
const datos = action.payload;
datos?.forEach((element) => {
arrayPueblosYexclus?.map(arel=>{
return element[arel] !== null
? (element[arel] = element?.[arel]
?.toString()
.trim()
.split(","))
: (element[arel] = []);
})
console.log(element.pueblos_T1,element.pueblos_T2,element.pueblos_T3);
});
setLoading(false);
setValorPrevio(state);
return { ...state, data: datos, loading: false, error: null };
break;
case "FETCH_ERROR":
return {
...state,
data: [],
loading: false,
error: Number(action.payload).toFixed(2),
};
break;
case "UPDATE_DATA":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.id) {
return { ...item, [action.property]: action.payload };
}
return item;
}),
};
break;
case "DELETE_FROM_VILLAGES":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.municipio_id) {
return {
...item,
[action.grupo]: item[action.grupo].filter(
(villa) => villa !== action.barrio
),
};
}
return item;
}),
};
break;
case "RISE_PRICES":
setValorPrevio(state);
return {
...state,
data: state?.data.map((item) => {
const updatedItem = { ...item };
const propertiesToUpdate = [
"precioT1_poco",
"precioT1_normal",
"precioT1_grua",
"precioT2_poco",
"precioT2_normal",
"precioT2_grua",
"precioT3_poco",
"precioT3_normal",
"precioT3_grua",
];
propertiesToUpdate.forEach((property) => {
if (
item[property] &&
item[property] !== 0 &&
item[property] !== "0"
) {
if (action.operation_type === "porcentual") {
updatedItem[property] = (
Number(item[property]) +
(Number(item[property]) * action.vlue) / 100
)
.toFixed(2)
.replace(/\.00$/, "");
setPercentage("");
}
if (action.operation_type === "normal") {
updatedItem[property] = (
Number(item[property]) + Number(action.vlue)
)
.toFixed(2)
.replace(/\.00$/, "");
setNormal("");
}
}
});
return updatedItem;
}),
};
break;
case "ADD_VILLAGE":
let villagesArray = action.villages.split(",").map(v=>v.trim().charAt(0).toUpperCase() + v.trim().slice(1));
return {
...state,
data: state.data.map((item,i) => {
if (item.id === action.selected_municipio) {
let villagescoincidence = villagesArray.filter(v => item[action.T].includes(v));
villagesArray = villagesArray.filter(v => !item[action.T].includes(v));
if (villagescoincidence?.length > 0) {
let coincidentes = villagescoincidence.join(", ");
console.log(`This message is appearing twice`);
}
return {
...item,
[action.T]: [...(item[action.T].filter(e => e !== "") || []), ...villagesArray],
};
}
return item;
}),
};
break;
default:
return state;
}
}
im having an issue with my code, its not populating the state object when state action is being performed. im new with redux
i have this code. so far that having an issue
this is the statement that will called the props.action fetchProjectFamilyList
case 'SubBusinessUnit':
setProductFamilyDetailsObj([])
if (selectedOption.id != 0) {
props.actions.fetchDepartment(selectedOption.id)
props.actions.fetchProjectFamilyList(selectedOption.id)
console.log(props)
}
setDropdownDataInState(resetData, 'Department')
setFormFields({
...formFields,
'OtherNamedInsuredIndustry': {
...formFields.OtherNamedInsuredIndustry,
value: ''
},
'NamedInsuredIndustry': {
...formFields.NamedInsuredIndustry,
value: "",
selectedId: 0
},
[fieldName]: {
...formFields[fieldName],
value: selectedOption.description, selectedId: selectedOption.id
}
});
break;
and this is the code for the commonreducer
export const fetchProjectFamilyList = createAsyncThunk(types.FETCH_PROJECT_FAMILY_LIST,
async (option, {getState, rejectWithValue}) => {
const reduxThunkConfig = {
checkStateData:getState().commonReducer.projectFamilyList && getState().commonReducer.projectFamilyList[option],
rejectWithValue
}
const APIConfig = {
URL: "eapi-referencedata/v1/lists/12?filterBySourceList=" + option + "&filterBySourceListValue=15",
method:"getData",
}
console.log('fetchProjectFamilyList')
return fetchCachedData(reduxThunkConfig, APIConfig);
}
)
im using the builder in my case of course inistailstate is set
const initialState = {
projectFamilyList:{},
}
builder.addCase(fetchProjectFamilyList.fulfilled, (state, action) => {
const subDivision = action.meta.arg;
return {
...state,
projectFamilyList:{
...state.projectFamilyList,
[subDivision]: action.payload},
}})
const commonActions = { ...actions, fetchProjectFamilyList }
export { commonActions, commonReducer}
this is the comment that accept the state as props. but the props productFamilyDetailsObj is empty object
<ProductFamilyComponent
productFamilyDetailsObj={productFamilyDetailsObj}
/>
function ProductFamilyComponent({ productFamilyDetailsObj }) {
return <div className="boxLayout">
<p className="smallHeading">Product Families</p>
{productFamilyDetailsObj.map((text, textIndex) => {
let index = textIndex;
return ( .... and so on
I hope theres someone who could help me resolving this. thank in advance.
Edit : Codesandbox here
Here is a simplified version on my parent component :
export default function Parent() {
// State
const [status, setStatus] = useState(1);
const [source, setSource] = useState('https://packagingeurope.com/downloads/7475/download/danone-05.03.20.jpg');
const [steps, setSteps] = useState([
{
title: 'Prediction Initiated',
value: 1,
loading: false,
completed: false,
active: false,
},
{
title: 'Prediction in Progress',
value: 2,
loading: false,
completed: false,
active: false,
},
{
title: 'Prediction Finished',
value: 3,
loading: false,
completed: false,
active: false,
},
]);
useEffect(() => {
if (status) {
const newSteps = steps;
newSteps[status - 1].active = true;
if (status > 1) {
newSteps[status - 2].completed = true;
}
if (status === 3) {
newSteps[status - 1].active = false;
newSteps[status - 1].completed = true;
}
setSteps(newSteps);
} else {
// Do nothing
console.log('No status match');
}
},
[status]);
return (
<div className="container-fluid">
<Timeline status={status} steps={steps} source={source} />
</div>
);
}
And here is my child component :
export default class Timeline extends React.Component {
renderSteps() {
const { steps } = this.props;
return steps.map((step, index) => {
console.log(step);
console.log(steps);
return (
<div key={`test${index}`}>
{step.title}
{step.active && <span>is active</span>}
{step.loading && <span>is loading</span>}
{step.completed && <span>is completed</span>}
</div>
);
});
}
render() {
const { status, steps, source } = this.props;
return (
<div className="timeline">
{this.renderSteps()}
</div>
</>
);
}
}
When i console.log steps props inside my child component, I see that they are correctly updated. (When status === 1, the first step active property is true)
But when i console.log step (inside my map function), none of the properties are updated. (When status === 1, the first step active property is false)
You can see on the capture below that something is not right (I already encountered this problem but unfortunately I can't remember how I solved it, and I am wondering if it's because of the useEffect hooks which I didn't use before this project.
Thank you for your help
Edit : Codesandbox here
Ok, I figured it out.
Since I got this code from another developer, I didn't understand everything that was implemented. I went to the React documentation to understand where the problem was coming from.
By passing a second parameter to the useEffect function, React can skip the effect and will not re-render the component for performance issues if there is not any change. As my status doesn't change (I change it manually for the moment), my component was not refreshing.
By adding [steps] width [status] inthe second parameter, everything works.
useEffect(() => {
if (status) {
const newSteps = steps;
newSteps[status - 1].active = true;
if (status > 1) {
newSteps[status - 2].completed = true;
}
if (status === 3) {
newSteps[status - 1].active = false;
newSteps[status - 1].completed = true;
}
setSteps(newSteps);
} else {
// Do nothing
console.log('No status match');
}
}, [status, steps]);
I have a nested state like :
bookingDetails = {
jobCards: [
{
details_id: '1',
parts: [
{...},
{...}
]
}
]}
Now I got the respective jobCards in component from props from parent component i.e detailsID by using useSelector:
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
I got a button that successfully adds new object in parts in respective jobCards but that doesnot update the UI.
My bookingDetails Reducer:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
jobCard.parts = [...jobCard.parts, { _id: uuid(), name: '' }]
}
return jobCard
})
}
use like this
const [isJobUpdated, setIsJobUpdated] = useState(false);
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
useEffect(() => {
setIsJobUpdated(!!jobCard.length);
}, [jobCard])
return (
<>
{isJobUpdated && <YourComponent />
</>
)
NOTE: this is not the best way to do. You might face re-render issue. Just to check if this solve your current issue.
Forgot to add return statement.
The reducer should have been:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
return {
...jobCard,
parts: [...jobCard.parts, { id: uuid(), name: ''}]
}
}
return jobCard
})
}
I'm learning redux and I was wondering how to delete one item from the state. I have this initial state:
export const getInitialState = () => {
let state = {
isLogged: false,
organizations: [],
userData: {},
activeIndex: -1,
currentRetrospective: {},
hasFetched: false
}
This is how the data lives inside organizations
case `${actions.ACTION_GET_USER_ORGS}_FULFILLED`: {
let activeIndex = 0
if (state.activeIndex !== -1) {
activeIndex = state.activeIndex
} else if (action.payload.data.length === 0) {
activeIndex = -1
}
return { ...state, activeIndex, organizations: action.payload.data, hasFetched: true }
}
Now, what I need to do is to delete one item from the retrospectives array in an organization. I tried this but it doesn't work. Is there a better way to do it?
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
const { organizations, activeIndex } = state
const newOrganizations = JSON.parse(JSON.stringify(organizations))
const activeOrganization = newOrganizations[activeIndex]
activeOrganization.retrospectives = activeOrganization.retrospectives
.filter((retro) => retro.id != action.retroId )
return { ...state, organizations: newOrganizations }
}
Thank you!
you can filter the organization array like this:
export default (state = getInitialState(), action) => {
switch (action.type) {
case `${actions.ACTION_DELETE_RETROSPECTIVE}_FULFILLED`: {
return {
...state,
organizations: state.organization.filter(retro =>
retro.id !== action.retroId }
}