I am new to react, I created a context, it has a function that updates the dictionary using reducer dispatcher. The function takes an object, this object is then converted to another object, and added to the dictionary.
when I try to print the dictionary immediately after calling the AddToDictionary function, I don't get the updated one. It is like the component is not waiting on the context to finish processing the AddToDictionary. How can I fix this??
import React, {
useContext,
useReducer,
} from "react";
import MapViewContext from "./mapview-context.js";
const mapViewEventContext = React.createContext({
AddFeatureToSelection: (newFeature, layer) => {},
FeatureSelectionTracker: {},
});
export const ViewContextHistoryProvider = (props) => {
const reducerFn = (state, action) => {
if (action.type === "SetSelectedFeaturesDictionary") {
let newArray = {};
newArray = { ...action.selectedFeaturesDictionary };
return {
CurrentFeatureSelection: { ...newArray },
};
}
if (action.type === "RemoveFromSelection") {
let newArray = {};
newArray = { ...state.CurrentFeatureSelection };
let newFtr = action.feature;
let lyr = action.layer;
if (
newArray[lyr.layerId] != undefined &&
newArray[lyr.layerId] != null &&
newArray[lyr.layerId].length != 0
) {
var indexOfFtr = newArray[lyr.layerId].findIndex(
(x) => x.ObjectId === newFtr.attributes["OBJECTID"]
);
if (indexOfFtr >= 0) {
let featuresArray = newArray[lyr.layerId];
featuresArray = featuresArray.splice(indexOfFtr);
newArray[lyr.layerId] = featuresArray;
}
}
return {
CurrentFeatureSelection: { ...newArray },
};
}
if (action.type === "AddToSelection") {
let newArray = {};
newArray = { ...state.CurrentFeatureSelection };
let newFtr = action.newFeature;
let lyr = action.featureLayer;
if (newArray[lyr.layerId] == undefined || newArray[lyr.layerId] == null) {
let ftr = CreateFeatureObject(newFtr, lyr);
newArray[lyr.layerId] = [ftr];
console.log("added feature and layer" ftr);
} else {
let oid = newFtr.attributes["OBJECTID"];
let exists = newArray[lyr.layerId].filter((x) => x.ObjectId === oid);
if (exists.length == 0) {
let ftr = CreateFeatureObject(newFtr, lyr);
newArray[lyr.layerId].push(ftr);
console.log("pushed feature " ftr);
}
}
return {
CurrentFeatureSelection: { ...newArray },
};
}
return {
CurrentFeatureSelection: state.CurrentFeatureSelection,
};
}
const [reducerTracker, dispachToolFn] = useReducer(reducerFn, {
CurrentFeatureSelection: {},
});
function CreateFeatureObject(ftr, layer) {
// returns object that gets added to the dictionary
}
const AddFeatureToSelectionHandler = (newFeature, layer) => {
dispachToolFn({
type: "AddToSelection",
newFeature: newFeature,
featureLayer: layer,
});
};
return (
<mapViewEventContext.Provider
value={{
AddFeatureToSelection: AddFeatureToSelectionHandler,
FeatureSelectionTracker: reducerTracker.CurrentFeatureSelection,
}}
>
{props.children}
</mapViewEventContext.Provider>
);
};
export default mapViewEventContext;
The Component calls the contexts method AddFeatureToSelection, then immediately when I try to log the context FeatureSelectionTracker I don't get the updated dictionary. basically the context AddFeatureToSelection method completes execution after I log the FeatureSelectionTracker, eventhough I am calling it first from the component.
CodePudding user response:
The purpose of React context is to avoid passing down a prop many levels through a component. All it does is define some global props that all components inside the consumer component can access using useContext
. Consider it as a "shortcut mechanism".
Without context
function App() {
const [theme, setTheme] = useState('dark');
return <Wrapper theme={theme}/>;
}
function Wrapper({theme}) {
return <Sidebar theme={theme}/>;
}
function Sidebar({theme}) {
return <div>
<DeepComponent theme={theme}/>
</div>;
}
function DeepComponent({theme}) {
return <button>
{theme === 'dark' ? 'Dark Mode' : 'Light Mode'}
</button>;
}
With context
const ThemeContext = createContext({});
function App() {
const [theme, setTheme] = useState('dark');
return <ThemeContext.Provider value={{theme, setTheme}}>
<Wrapper/>
</ThemeContext.Provider>;
}
function Wrapper() {
return <Sidebar/>;
}
function Sidebar() {
return <DeepComponent/>;
}
function DeepComponent() {
const {theme, setTheme} = useContext(ThemeContext);
return <button
onClick={() => {setTheme(theme === 'dark' ? 'light' : 'dark')}}
>
{theme === 'dark' ? 'Dark Mode' : 'Light Mode'}
</button>;
}
If the context contains a function that updates some state, that doesn't result in the state immediately propagating to the context object. The context is just used as a "vehicle" to get a value or function somewhere more efficiently (without unnecessarily passing props through intermediate levels).
You're question is missing the implementation of AddFeatureToSelection
, it's used with useReducer
so I guess the implementation details won't matter. If you do setState
, or dispatch to a reducer, it also doesn't immediately update the state you already have in scope. That's not a problem because these setters are only meant to be used in event listeners, never during a render itself. So there's nothing to "fix" here.
Whether you use context or not doesn't change that behavior.
How can I fix this?
If you need to set a value to the state, and also use the new value further in the function, after setting it, just assign it to a variable.
const newState = 'new';
setMystate(newState);
console.log(newState);
For a reducer you of course can't do that, however this is likely a non issue. You're only running into it because you want to console.log
the latest state (I suppose that's what you mean with "print"). The only purpose of this state is to be used by your React component in the next render.
If your component needs side effects based on its state, you could use an effect. For example you want to count the dictionary items and reflect it in the page title. This will always have the latest value.
useEffect(
() => {
document.title = `Selected features: ${state.CurrentFeatureSelection.length}`;
},
[state.CurrentFeatureSelection] // Make sure this is stable over renders.
)