I am new to react and need some help on understanding using hooks. I have 3 views I want to render based on 3 different components I want to render based on different TEXT buttons. The buttons would in the same location throughout the components but different text on them!
Component the view will switch from:
const Original = () => {
const [button1, setButton1] = React.useState("button1");
const [button2, setButton2] = React.useState("button2");
const testOriginal = () => {};
const originalView = () => {
setButton1("buttonview1");
setButton2("buttonview2");
return "<div>View 0<button></button><button></button></div>";
};
const view2 = () => {
setButton1("button11view2");
setButton2("button12view2");
return "<div>View 2<button></button><button></button></div>";
};
const view3 = () => {
setButton1("button21view3");
setButton2("button22view3");
return "<div>View 2<button></button><button></button></div>";
};
const ActiveView = () => {
switch (active) {
case 1:
return originalView;
case 3:
return view3;
default:
return view2;
}
};
return ({
ActiveView()
});
};
export default Original;
const View1 = () => {
return <div>View 1</div>;
};
const View2 = () => {
return <div>View 2</div>;
};
export default View2;
const View3 = () => {
return <div>View 3</div>;
};
export default View3;
Related
I want to use different context in a common component.
There are 2 providers and 1 common component.
The common component uses different data, and 2 providers have each data indivisually.
e.g.
const useMyProvider1 = () {
return useContext(MyContext1)
}
const MyProvider1 = ({children}) => {
const [data1, setData1] = useState(initial1)
return <MyContext1.Provider value={[data1, setData1]}>{children}</MyContext1.Provider>
}
const useMyProvider2 = () {
return useContext(MyContext2)
}
const MyProvider2 = ({children}) => {
const [data2, setData2] = useState(initial2)
return <MyContext2.Provider value={[data2, setData2]}>{children}</MyContext2.Provider>
}
const Component1 = () => {
return (
<MyProvider1>
<CommonComponent /> // CommonComponent uses data1
</MyProvider1>
)
}
const Component2 = () => {
return (
<MyProvider2>
<CommonComponent /> // Same component but this uses data2
</MyProvider2>
)
}
const CommonComponent = () => {
const {data, setData} = /* I want to use only one provider useMyProvider1 or useMyProvider2 depends on wrapped provider */
return (
<div>this is one of {data}</div>
)
}
It is an idea but not good I think, because CommonComponent depends on each providers.
I want to depend on only wrapped provider.
const CommonComponent = ({providerType}) => {
const {data, setData} = providerType === 'type1' ? useMyProvider1() : useMyProvider2()
return (
<div>this is one of {data}</div>
)
}
Is there any good solutions?
I am working on a react app where I have a userSettings screen for the user to update their settings on clicking a save button. I have two sliding switches that are saved and a dispatch function is ran to post the data.
Each switch has their own toggle function, and all the functions run at the same time.
My problem is that when I pass the userSettings object to the child component and run both functions, it runs with the wrong values which results in the data not saving properly.
Here is my code:
Parent component that has the toggle functions, handles the state, and set the userSettings object:
class SideMenu extends React.PureComponent {
constructor(props) {
super(props);
const userToggleSettings = {
cascadingPanels: this.props.userSettings.usesCascadingPanels,
includeAttachments: this.props.userSettings.alwaysIncludeAttachments,
analyticsOptIn: false,
};
this.state = {
userToggleSettings,
};
}
toggleIncludeAttachments = () => {
this.setState((prevState) => ({
userToggleSettings: {
...prevState.userToggleSettings,
includeAttachments: !prevState.userToggleSettings.includeAttachments,
},
}));
};
toggleCascadingPanels = () => {
this.setState((prevState) => ({
userToggleSettings: {
...prevState.userToggleSettings,
cascadingPanels: !prevState.userToggleSettings.cascadingPanels,
},
}));
};
includeAttachmentsClickHandler = () => {
this.toggleIncludeAttachments();
};
cascadingPanelsClickHandler = () => {
this.toggleCascadingPanels();
};
render() {
const darkThemeClass = this.props.isDarkTheme ? "dark-theme" : "";
const v2Class = this.state.machineCardV2Enabled ? "v2" : "";
const phAdjustmentStyle = this.getPersistentHeaderAdjustmentStyle();
const closeButton =
(this.state.machineListV2Enabled &&
this.props.view === sideMenuViews.USER_SETTINGS) ||
(!this.props.wrapper && this.props.view === sideMenuViews.SETTINGS);
return (
<div className="sideMenuFooter">
<SideMenuFooterContainer
userToggleSettings={this.state.userToggleSettings} //HERE IS USER_SETTINGS PASSED
/>
</div>
);
}
}
The child component that dispatches the data
SideMenuFooterContainer:
export function mapStateToProps(state) {
return {
translations: state.translations,
userSettings: state.appCustomizations.userSettings,
};
}
export function mapDispatchToProps(dispatch) {
return {
toggleCascadingPanels: (hasCascadingPanels) =>
dispatch(userSettingsDux.toggleCascadingPanels(hasCascadingPanels)),
toggleIncludeAttachments: (hasIncludeAttachments) =>
dispatch(userSettingsDux.toggleIncludeAttachments(hasIncludeAttachments)),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SideMenuFooter);
SideMenuFooterView (where it calls the dispatch):
const saveUserSettings = (props) => {
console.log("userSettings ==>\n");
console.log(props.userToggleSettings);
props.toggleIncludeAttachments(props.userToggleSettings.includeAttachments);
props.toggleCascadingPanels(props.userToggleSettings.cascadingPanels);
};
const cancelButtonClickHandler = (props) => {
if (props.viewTitle === props.translations.USER_SETTINGS) {
return () => props.closeSideMenu();
}
return () => props.viewBackButtonCallback();
};
const doneSaveButtonsClickHandler = (props) => {
return () => {
saveUserSettings(props);
props.closeSideMenu();
};
};
const SideMenuFooter = (props) => {
return (
<div className="side-menu-footer">
<div className="side-menu-footer-container">
<button
className="btn btn-secondary"
onClick={cancelButtonClickHandler(props)}
>
{props.translations.CANCEL}
</button>
<button
className="btn btn-primary"
onClick={doneSaveButtonsClickHandler(props)}
>
{props.translations.SAVE}
</button>
</div>
</div>
);
};
export default SideMenuFooter;
Dispatch functions:
export function toggleIncludeAttachments(hasIncludeAttachments) {
return async (dispatch, getState) => {
const { translations, appCustomizations } = getState();
const updatedUserSettings = {
...appCustomizations.userSettings,
alwaysIncludeAttachments: hasIncludeAttachments,
};
try {
await saveAppCustomizationByName(
CUSTOMIZATIONS.USER_SETTINGS,
updatedUserSettings
);
dispatch(setSettings(updatedUserSettings));
} catch (err) {
dispatch(
bannerDux.alertBanne({
description: "FAILED TO UPDATE USER DATA",
})
);
}
};
}
export function toggleCascadingPanels(hasCascadingPanels) {
return async (dispatch, getState) => {
const { translations, appCustomizations } = getState();
const updatedUserSettings = {
...appCustomizations.userSettings,
usesCascadingPanels: hasCascadingPanels,
};
try {
await saveAppCustomizationByName(
CUSTOMIZATIONS.USER_SETTINGS,
updatedUserSettings
);
dispatch(setSettings(updatedUserSettings));
} catch (err) {
dispatch(
bannerDux.alertBanner({
description: "FAILED TO UPDATE USER DATA",
})
);
}
};
}
Here is a demo:
When I set them both to false and console log the values, it looks like it is getting the correct values, but in the network call, it is getting different values on different calls
console.log output:
First network call to save data header values:
Second network call to save data header values:
NOTE: The dispatch functions work correctly, they where there before all the edits. I am changing the way it saves the data automatically to the save button using the same functions defined before.
Did I miss a step while approaching this, or did I mishandle the state somehow?
Please can someone tell me what is such as react component that returns a bunch of functions and which uses react hooks inside of it ?
Even a react functional component is supposed to return jsx or any other template , no ?
Is such as component stated in react documentation ? how is it called ?!
export default function useViewFilter() {
const dispatch = useDispatch();
const { boardId, viewId } = useParams<RouteParamsTypes>();
const [selectedFacets, setSelectedFacets] = useState<SelectedFacets>({});
const [propertiesData, setPropertiesData] = useState<PropertyData[]>([]);
const [propertiesFacetingTypes, setPropertiesFacetingTypes] = useState<PropertiesFacetingTypes>({});
const neededData = useSelector((state: IFrontAppState) => selectFilterHookNeededData(state, { boardId, viewId }));
const isReady = useSelector((state: IFrontAppState) => isViewModelLoaded(state, boardId, viewId));
const storeFacetsFilters = useSelector((state: IFrontAppState) => getViewFacetsFilters(state, boardId, viewId));
const storeFacetingTypes = useSelector((state: IFrontAppState) =>
getViewPropertiesFacetingTypes(state, boardId, viewId)
);
const dataRef = useRef({
initialized: false
});
useEffect(() => {
handleMakeNewSearch(selectedFacets, false);
}, [neededData]);
// last state before refresh
useEffect(() => {
if (isReady && !dataRef.current.initialized) {
dataRef.current.initialized = true;
setSelectedFacets(storeFacetsFilters ? (storeFacetsFilters as SelectedFacets) : {});
setPropertiesFacetingTypes(storeFacetingTypes ? storeFacetingTypes : {});
handleMakeNewSearch(storeFacetsFilters as SelectedFacets, false);
}
}, [isReady, storeFacetsFilters, storeFacetingTypes, dataRef]);
const handleClear = () => {
const selectedFacets = {};
setSelectedFacets(selectedFacets);
handleMakeNewSearch(selectedFacets, true);
dispatch(
actions.ClearViewFilters({
BoardId: boardId,
ViewId: viewId
})
);
};
const handleMakeNewSearch = (selectedFacets: SelectedFacets, doUpdateInBackend: boolean) => {
const { propertiesIds, properties, orderedEntities, boardMembersObj } = neededData;
if (properties && propertiesIds?.length) {
const orderedProperties = propertiesIds?.map(pId => ({
Id: pId,
...properties[pId]
}));
const itemsJsData = makeItemsJsDataFromAllEntities(orderedEntities, orderedProperties, boardMembersObj);
const newSearchResult = makeSearchAggregations(itemsJsData, orderedProperties, selectedFacets);
// set filtred items
if (doUpdateInBackend) {
const filtredEntitiesIds = newSearchResult.items.map(x => x.id) as string[];
dispatch(
actions.UpdateViewFilters({
BoardId: boardId,
ViewId: viewId,
FacetsFilters: selectedFacets,
FiltredEntitiesIds: doesThereIsFeltering(selectedFacets) ? filtredEntitiesIds : null,
PropertiesFacetingTypes: propertiesFacetingTypes
})
);
}
// update filter panel
const newPropertiesData = makeNewPropertiesData(
propertiesIds,
properties,
newSearchResult.aggregations,
propertiesFacetingTypes
);
setPropertiesData(newPropertiesData);
}
};
return {
propertiesData,
propertiesFacetingTypes,
handleClear,
setPropertiesFacetingTypes
};
}
It's a custom hook.
it's one of the ways (nowadays maybe the standard way) of extracting logic in React function components.
Basically, I have one component, let's call it component1 and a second component, which has been created by duplicating the first one called component2. I had to duplicate it, because some objects inside it had to be altered before sending them to the further components.
On one page I have an onClick event which triggers component1 which opens a modal and on another page, component2 is trigger the same as for the first one.
The problem occurs here, if I'm on the second page where the modal from component2 is opened and I refresh the page, both components are called, of course component1 is the first one called and the state is altered by this component which makes me not having the desired information in the second component.
As far as I understood, because of the fact that in both components, mapStateToProps is altering my state, both components are called. Not really sure though that I understood right.
Here is my component1 summary:
class LivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
componentDidMount() {
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
if (queryParam === null) {
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.LEADERBOARD);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.subscribe(endpointNames.STATISTICS);
}
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.unsubscribe(endpointNames.PLAYERS);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
isOpen, scoringPlayer, isQualification, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
if (isQualification) {
return null;
}
return (
<ReactModal isOpen={isOpen} onCloseCb={this.hide}>
<div className="live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId } = ownProps;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const scoringLeaderboard = state.scoring[endpointNames.LEADERBOARD];
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringStats: state.scoring[endpointNames.STATISTICS],
scoringCourse: state.scoring[endpointNames.COURSE],
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const LivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(LivePlayerModal);
export default LivePlayerCardContainer;
Here is my component2 summary :
class QualificationLivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
shouldComponentUpdate(nextProps) {
return nextProps.isQualification;
}
componentDidMount() {
ScoringLoader.subscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.subscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.unsubscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
scoringPlayer, summaryFinal, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
const open = (queryParam === 'true');
if (scoringPlayer !== undefined && scoringPlayer !== null) scoringPlayer.id = scoringPlayer.entryId;
return (
<ReactModal isOpen={open} onCloseCb={this.hide}>
<div className="qual-live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId, location } = ownProps;
const locationIdFromQueryParam = UrlHelper.getParamFromLocation(LOCATION_ID, window.location);
const locationId = location !== null ? location.locationId : locationIdFromQueryParam;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const summaryRegional = state.scoring[endpointNames.SUMMARY_REGIONAL];
const summaryFinal = state.scoring[endpointNames.SUMMARY_FINAL];
const scoringLeaderboard = getLeaderboardBasedOnLocation(locationId, summaryFinal, summaryRegional);
const currentRound = getCurrentRound(locationId, summaryFinal, summaryRegional);
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringCourse: getScoringCourseFromQualificationFeed(),
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix,
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const QualificationLivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(QualificationLivePlayerModal);
export default QualificationLivePlayerCardContainer;
Basically, the problem i ve got here, is that in state.scoring I do not have the information for the endpoints present in the return statement of the render method before the page finishes the refresh process, which later on makes my app to break.
Hope I've been clear enough.
Is there a solution for waiting the endpoints to get called or even not loading the first component at all?
The following selectManageAdvancedUserFilters selector cause to render my component twice, but without that selector it renders only one time
export const selectManageAdvancedUserFilters = typeCode => {
return createSelector([selectUserFilters(typeCode)], userFilters => {
const manageAdvancedFilters = userFilters.map((filter, index) => {
return {
index: index + 1,
label: filter.name,
value: filter.name,
id: filter.id
};
});
return manageAdvancedFilters;
});
};
export const selectUserFilters = typeCode => {
return createSelector([selectAllUserFilters], allUserFilters =>
allUserFilters.filter(allUserFilter => allUserFilter.type === typeCode)
);
};
export const selectAllUserFilters = createSelector(
[selectControls],
controls => {
return controls && controls.advancedFilters ? controls.advancedFilters : [];
}
);
export const selectControls = state => {
return state.controls.data;
};
here is the usage of selector
const unallocatedFilters = useSelector(
selectDropdownSuggestionFilters('AF1')
)
What is the reason cause to rerender with this selector?
I think the re-render is caused by the implementation details of the dynamic parameter, typeCode.
See the link below:
https://github.com/reduxjs/reselect/issues/392