This is my first time working with context. So, I've about 6 screens and on each screen, the user selects some options and on the 6th screen, I want all the information that they have selected/entered on previous screens.
I've created a class called context.js in which I've done this:
export const bookData = React.createContext({
bookingData: {
"app_date": 0,
"app_est": "",
"app_phone": "",
"app_ic": "",
"app_timeslots": {},
"app_hour": 0,
"app_est_url": "",
"app_year": 0,
"app_email": "",
}
})
This is the payload I'll need on the 6th screen. Now on-screen 1 I've imported it as import BookContext from '../utils/context' and this screen has a next button in which I want to pass data to app_email, app_phone, and app_ic which I've in variables that I populated using. useState() on this screen:
<MainActionButton title={'Next'} pressEvent={() => {
alert(email + phone + ic)
}} />
Here email, phone, and ic are set using a useState(). So how do I use BookContext that I've imported inside the pressEvent to access/update the context?
I might be completely off track here as I don't fully understand the concept of context yet so a brief answer will be appericiated.
For updating the value of the context you can follow this simple pattern:
Maintain a state in the component that has the provider. For example, you can have something like this:
const initialState = {
bookingData: {
"app_date": 0,
"app_est": "",
"app_phone": "",
"app_ic": "",
"app_timeslots": {},
"app_hour": 0,
"app_est_url": "",
"app_year": 0,
"app_email": "",
}
};
const [bookData, setBookData] = react.useState(initialState);
Pass the bookData and setBookData as context's value:
<BookData.Provider value={{state: bookData, updateState: setBookData}}></BookData.Provider>
Now in the child components you can use the passed object(value) for accessing and updating the state.
const { state, updateState } = React.useContext(BookData)
you would need to create an "api" inside your context to update records (e.g. bookContext.update(payload)), for example:
import * as React from "react";
let ContextOne = React.createContext();
let initialState = {
count: 10,
currentColor: "#bada55"
};
let reducer = (state, action) => {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return { ...state, count: state.count + 1 };
case "decrement":
return { ...state, count: state.count - 1 };
case "set-color":
return { ...state, currentColor: action.payload };
}
};
function ContextOneProvider(props) {
let [state, dispatch] = React.useReducer(reducer, initialState);
let value = { state, dispatch };
return (
<ContextOne.Provider value={value}>{props.children}</ContextOne.Provider>
);
}
let ContextOneConsumer = ContextOne.Consumer;
export { ContextOne, ContextOneProvider, ContextOneConsumer };
i wrap my app in my custom context/provider:
import { ContextOneProvider } from "./ContextOne";
import { App } from "./App";
ReactDOM.render(
<ContextOneProvider>
<App />
</ContextOneProvider>,
document.getElementById("root")
);
now when I consume ContextOne:
function App() {
let { state, dispatch } = React.useContext(ContextOne);
// dispatch will update the context and re-render
}
I wrote a walkthrough about that in:
https://dev.to/oieduardorabelo/react-hooks-how-to-create-and-update-contextprovider-1f68
One library that I recommend to abstract React.Context is:
https://github.com/jamiebuilds/unstated-next
Related
I don't understand something in react-redux.
I have created a slice called Introduction look below:
import { createSlice } from "#reduxjs/toolkit";
import { IntroductionFields } from "../helpers/interface";
const initialState: IntroductionFields = {
fullName:'',
subtitle:'',
description:'',
location:'',
email:'',
portfolio: {name:'' , url:''},
project: {name: '' , url: ''},
learning: '',
collaborating: '',
else: '',
}
const Introduction = createSlice({
name: 'intro',
initialState,
reducers:{
update(state, actions){
const key = actions.payload.name;
const val = actions.payload.value;
state.fullName = val; // WORK
state = {...state, [key]: val} // NO WORK
console.log(actions.payload.name , " " , actions.payload.value);
},
}
})
export const IntroductionActions = Introduction.actions;
export default Introduction;
and I have two more components,
first component has fields (inputs) and every field has an onChange that calls the dispatch and uses update on the reducer that I created in the introduction slice and I send the key and value, see below.
const Intro: React.FC<Props> = ({ moveForward }) => {
const dispatch = useDispatch();
const changeHandler = (event: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => {
const {name , value} = event.target;
dispatch(IntroductionActions.update({name, value}))
}
return (.... // HERE I HAVE INPUTS...)
}
In the second component I want to get the values from the Introduction slice so if I change some fields in Intro component, I want to see the changes in my Preview component.
import React, { useEffect } from 'react'
import classes from './Preview.module.scss';
import { useSelector } from 'react-redux';
import { RootState } from '../../../store/store';
const Preview = () => {
const introduction = useSelector((state:RootState) => state.intro);
return (
<div className={classes.previewContainer}>
{introduction.fullName && <h1>Hi! My name is {introduction.fullName}</h1>}
</div>
)
}
export default Preview
If you'll look to the first code section
you will see these two lines.
state.fullName = val; // WORK
state = {...state, [key]: val} // NO WORK
If I directly write into the field in state it works prefect, but if I try to do the second line it doesn't work...
I want it to be dynamic that is why I want to use the second line...
You can set the state like this since there is no need to copy the whole state to the new state.
update(state, actions){
const key = actions.payload.name;
const val = actions.payload.value;
state[key] = val;
},
The section Create a Redux State Slice will explain in depth how/why
Dispatch the action with object as payload
dispatch(IntroductionActions.update({fullName: name, subtitle: subtitle}))
and your reducer function will be like this
update(state, actions){
return ({...state, ...actions.payload})
}
Here based on the payload, state will get updated , here fullName and subtitle values will get updated.
I've got a question. I have a custom hook like this:
import { isNullOrUndefined } from '../../../../../../utils/utils';
import { useSelector } from 'react-redux';
export const useGoodsSetupWizardController = () => {
const productSetupWizard = useSelector((state) => state.productSetupWizard.value);
function isNextButtonDisabled() {
let returnValue = false;
switch(productSetupWizard?.step) {
case 2:
if(isNullOrUndefined(productSetupWizard?.product?.productName) || productSetupWizard?.product?.productName.trim().length === 0) {
returnValue = true;
}
}
return returnValue;
}
return {
isNextButtonDisabled,
};
}
As you can see I'm using useSelector to get the current value of productSetupWizard.
In my other components and hooks all is working fine using this way.
The slice is this one here:
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
value: {
step: 1,
product: null
},
};
export const productSetupWizardSlice = createSlice({
name: 'productSetupWizard',
initialState,
reducers: {
setProductSetupWizard: (state, action) => {
state.value = action.payload
},
increaseStep: (state, action) => {
state.value.step++;
},
decreaseStep: (state, action) => {
state.value.step--;
}
},
});
export const { setProductSetupWizard, increaseStep, decreaseStep } = productSetupWizardSlice.actions;
export default productSetupWizardSlice.reducer;
I don't know if you need to see the store file here to answer my question. If so, please let me know. I will add them to my question then.
The question I have, it seems not be working in a custom hook like the code above.
It gives me always the initial value of productSetupWizard and not updates on it.
Is it because the custom hook is initialized once on the component body with the initial value of productSetupWizard?
So my question is, is there any special usage in custom hooks necessary to get the state values from React-redux (toolkit)?
So the value of productSetupWizard in method isNextButtonDisabled() is always the initial value:
value: {
step: 1,
product: null
},
Here is the complete way of importing the different custom hooks I use in my project.
I have a hook called ProductSetupWizard. This is my UI. This ProductSetupWizard is importing the function getController from the custom hook called useProductTypeSetupWizard:
import { useProductTypeSetupWizard } from '../../../../factories/productSetup.factory';
function ProductSetupWizard(props) {
const [productSetupController, setProductSetupController] = useState(null);
...
const { getController } = useProductTypeSetupWizard();
...
useEffect(() => {
setProductSetupController(getController(productTypeId));
},[productTypeId]);
return (
<>
...
</>
);
}
The useProductTypeSetupWizard hook is loading and returning the useGoodsSetupWizardController depending on the given productTypeId:
import { isNullOrUndefined } from "../utils/utils";
import { useGoodsSetupWizardController } from '../components/managementInterface/shop/tabs/productSetupWizard/setups/goodsSetupWizard.controller';
export const useProductTypeSetupWizard = () => {
const {
getComponentGoodsController,
isNextButtonDisabledGoodsController,
needsNextStepToBeSkippedGoodsController
} = useGoodsSetupWizardController();
function getController(productTypeId) {
if(isNaN(parseInt(productTypeId))) {
return null;
}
switch(parseInt(productTypeId)) {
case 1:
return {
getComponent: getComponentGoodsController,
isNextButtonDisabled: isNextButtonDisabledGoodsController,
needsNextStepToBeSkipped: needsNextStepToBeSkippedGoodsController
};
case 3:
//apply like useGoodsSetupWizardController
}
}
return {
getController
};
}
i got two values i.e.company and id from navigation.
let id = props.route.params.oved;
console.log("id-->",id);
let company = props.route.params.company;
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
in the component create a function that updates the values
const updateReducer = () => {
dispatch(changeCompany(props.route.params.oved, props.route.params.company))
}
then call the function in react navigation lifecycle event
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
updateReducer()
});
return unsubscribe;
}, [navigation])
its possible that a better solution would be to update the reducer before the navigation happens and not pass the data in the params but rather pull it from redux but this is the answer to the question as asked
I have array in redux. I am showing datas on Flatlist. However, When I edited array data , flatlist not re-render. How can i solve this problem? I checked my redux and is working fine
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
Flatlist code;
<FlatList
ref={(list) => this.myFlatList = list}
data={this.props.notes}
showsVerticalScrollIndicator={false}
renderItem={({item, index})=>(
)}
removeClippedSubviews={true}
extraData={this.props.notes}
/>
mapStateToProps on same page with Flatlist
const mapStateToProps = (state) => {
const { notes } = state
return { notes }
};
Reducer
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return state = action.payload;
default:
return state
}
};
export default notesReducer;
The reason it's not updating is because you're not returning a new array. The reference is same.
Return the updated state like return [...state,action.payload]
The reason it's not updating the data correctly is because the mutation.
The problematic code is this part.
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
It should be in this way
const { notes, editNotes } = this.props;
const newNotes = [...notes];
const { index } = this.state;
newNotes[index] = {
//update data
}
editNotes(newNotes);
You can fix the issue in many ways but the wrong part I see in your code is Reducer. As per the standard, your reducer should be a Pure Function and the state should not mutate.
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
...action.payload;
},
default:
return state
}
};
export default notesReducer;
This should resolve your issue.
Suggestion:
Try to create a nested hierarchy in redux like
const initialState = {
notes: [],
};
const notesReducer = (state = initialState, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
notes: [
...state.notes,
...action.payload.notes,
],
},
default:
return state
}
};
export default notesReducer;
My store is the next:
const initialState = {
all: {
"name": "",
"datas": {
"data1": {
"subdata1": [],
"subdata2": [],
},
"data2": {
"subdata1": [],
"subdata2": [],
},
}
}
}
I have the next Components:
* All: main Component, map to Data with all.datas
* Data: show data1 or data2 or data3. map to Subdata with data1 and data2 ...
* Subdata: show list subdata1
I have a redux connect in Subdata, and I want that the reducers, instead of return general state, return a subdata state.
For example:
Subdata.js:
class Subdata extends React.Component {
// ....
}
function mapStateToProps(state, ownProps) {
return {
subdata: ownProps.subdata
};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(Actions, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(Subdata);
reducers.js:
const subdata = (state = {}, action) => {
// I want that state return: subdata, in this case, []
// instead of all: {...}
const copy = JSON.parse(JSON.stringify(state));
switch(action.type) {
case "ADD_SUBDATA":
// In this moment: I need pass a lot of vars: data_id and subdata_id
my_subdata = state.datas[data_id][subdata_id]
// I want get directly the subdata
my_subdata = state.subdata
default:
return state;
}
}
Is this possible? How?
Thanks you very much.
Yes it is possible.
You could consider using dot-prop-immutable or Immutable.js in your reducer to selectively operate on a "slice" of your store.
Pseudo code using dotProp example:
// Getter
dotProp.get({foo: {bar: 'unicorn'}}, 'foo.bar')
Signature:
get(object, path) --> value
I notice in your code you are using the following to "clone" your state:
const copy = JSON.parse(JSON.stringify(state));
This is not optimal as you are cloning completely all your state object, when instead you should operate only on a part of it (where the reducer operates, specially if you are working with combined reducers).