What does the subscribers array do in the HOC withUser? - javascript

Just as the title says, I don't understand in this code what does the subscribers.
import React from "react";
const stateFromStore = sessionStorage.getItem('user');
let state = stateFromStore ? JSON.parse(stateFromStore) : null;
const subscribers = [];
const unsubscribe = subscriber => {
const index = subscribers.findIndex(subscriber);
index >= 0 && subscribers.splice(index, 1);
};
const subscribe = subscriber => {
subscribers.push(subscriber);
return () => unsubscribe(subscriber);
};
export const withUser = Component => {
return class WithUser extends React.Component {
componentDidMount() {
this.unsubscribe = subscribe(this.forceUpdate.bind(this));
}
render() {
const newProps = { ...this.props, user: state };
return <Component {...newProps} />;
}
componentWillUnmount() {
this.unsubscribe();
}
};
};
export const update = newState => {
state = newState;
sessionStorage.setItem('user', state ? JSON.stringify(state) : null);
subscribers.forEach(subscriber => subscriber());
};
I get the part about sessionStorate but I don't understand the use of the subscribers array.
This is part of an example about how to use passport with react https://github.com/HackedByChinese/passport-examples/tree/master/example-simple-react/client/src

Related

How to render if only received props in a react component

PropTable should render only if receives props from other component
const PropTable = (props) => {
const { availableProp } = props;
...
return(...)
}
This'd get your job done
const PropTable = (props) => {
const { availableProp } = props;
...
return availableProp ? (...) : null
}

React useState can't be set in useEffect

I am learning react and trying set object from queryStr for later reuse,
can't set searchFilter in useEffect
this line prinst null:
console.log(searchFilter.transactionId)//prints null
interface TransactionSearchFilter {
transactionId?: number;
}
const TransactionList: any = (props: any) => {
const queryStr = location.search.substring(1);
const [searchFilter, setSearchFilter] = React.useState<TransactionSearchFilter>({
transactionId: null,
});
const parseQueryParams = () => {
const queryObj = QueryString.parse(queryStr);
console.log(queryObj.transactionId)//prints 10
setSearchFilter({ ...searchFilter, ...queryObj });
console.log(searchFilter.transactionId)//prints null
};
React.useEffect(() => {
parseQueryParams();
}, []);
return (<div>Hello World</div>);
}; export default TransactionList;

Double rendering with react custom hook

I am trying to implement custom global state hook based on the article here State Management with React Hooks — No Redux or Context API. I keep getting double renders. It seems to be with the following piece of code:
function useCustom() {
const newListener = useState()[1];
effect(() => {
this.listeners.push(newListener);
return () => {
this.listeners = this.listeners.filter(
listener => listener !== newListener
);
};
}, []);
return [this.state, this.setState, this.actions];
}
If you console log inside this piece of code you can see it running twice at initial render and also twice every time you update the hook.
Any help on how to fix this would be much appreciated.
Here is the full code:
CodeSandbox
import React, { useState, useEffect, useLayoutEffect } from "react";
const effect = typeof window === "undefined" ? useEffect : useLayoutEffect;
function setState(newState) {
if (newState === this.state) return;
this.state = newState;
this.listeners.forEach(listener => {
listener(this.state);
});
}
function useCustom() {
const newListener = useState()[1];
effect(() => {
this.listeners.push(newListener);
return () => {
this.listeners = this.listeners.filter(
listener => listener !== newListener
);
};
}, []);
return [this.state, this.setState, this.actions];
}
function associateActions(store, actions) {
const associatedActions = {};
if (actions) {
Object.keys(actions).forEach(key => {
if (typeof actions[key] === "function") {
associatedActions[key] = actions[key].bind(null, store);
}
if (typeof actions[key] === "object") {
associatedActions[key] = associateActions(store, actions[key]);
}
});
}
return associatedActions;
}
const useGlobalHook = (initialState, actions) => {
const store = { state: initialState, listeners: [] };
store.setState = setState.bind(store);
store.actions = associateActions(store, actions);
return useCustom.bind(store, React);
};
export default useGlobalHook;
Then set up the store like so:
import useGlobalState from './useGlobalState';
const initialState = false;
const useValue = useGlobalState(initialState);
export default useValue;
And the component
import React from 'react';
import useValue from '../store/useValue';
const Component1 = () => {
const [value, setValue] = useValue();
console.log('rendered component');
return (
<div>
<p>Value1: {value ? 'true' : 'false'}</p>
<button onClick={() => setValue(!value)}>Toggle Me</button>
</div>
);
};
export default Component1;

The action is undefined or invisible even if it seems written properly

I have to use useDispatch() for my toggle buttons so I have to refractor them from react to redux state. I was following the tutorial of basics of Redux and I think I have done that properly but when I try to at least useSelector to display the redux'state of button it doesnt show anything.
So here is my code:
// types.js in actions folder
export const TOGGLE = "TOGGLE";
// buttonActions in actions folder
export const toggle = () => {
return {
type: 'TOGGLE'
};
};
// buttonReducer in reducers folder
const buttonReducer = (state = true, action) => {
switch(action.type) {
case 'TOGGLE':
return !state;
default:
return state;
};
};
export default buttonReducer;
And the buttonReducer is imported into combineReducers which go to store.
The component code:
import React, { useState, useEffect } from 'react'
import isloff from './mainpage_imgs/isloff.png'
import islon from './mainpage_imgs/islon.png'
import PropTypes from "prop-types";
import { connect, useDispatch, useSelector } from "react-redux";
import { toggle } from '../../actions/buttonActions'
const Islbutton = props => {
const [open, setOpen] = useState(true);
const [role, setRole] = useState('');
useEffect(() => {
if (props.auth.user)
{
setRole(props.auth.user.role);
}
}, []);
const test = useSelector(state => state.button);
const checkRole = (role) => {
if (role === 'Menager' || role === 'Technolog')
{
return true }
else
{
return false
};
}
const toggleImage = () => {
if(checkRole(role)) {
setOpen(!open)
};
}
const getImageName = () => open ? 'islOnn' : 'islOfff'
const dispatch = useDispatch();
return(
<div>
<img style={islplace} src={open ? islon : isloff }
onClick={()=> dispatch(toggle())} />
</div>
);
}
Islbutton.propTypes = {
button: PropTypes.func.isRequired,
auth: PropTypes.obj.isRequired
};
const mapStateToProps = state => ({
button: state.button,
auth: state.auth
});
export default connect(mapStateToProps, {}), (Islbutton);
Based on your latest comments and my understanding of your use case I may suggest following distilled approach:
//dependencies
const { render } = ReactDOM,
{ createStore } = Redux,
{ connect, Provider } = ReactRedux
//action creators
const SET_ROLE = 'SET_ROLE',
MANAGER_APPROVED = 'MANAGER_APPROVED',
setRole = role => ({type:SET_ROLE, role}),
mngAppr = () => ({type:MANAGER_APPROVED})
//initial state, reducer, store
const initialState = {role:'Technolog', approved:false},
appReducer = (state=initialState, action) => {
switch(action.type){
case SET_ROLE : {
const {role} = state,
{role: newRole} = action
return {...state, role: newRole}
}
case MANAGER_APPROVED : {
const {approved} = state
return {...state, approved: !approved}
}
default: return state
}
},
store = createStore(appReducer)
//ui component to emulate toggling roles
const SwitchRoles = ({currentRole, switchRole}) => (
<div>
<label><input type="radio" name="role" value="Manager" onChange={e => switchRole(e.target.value)} />Manager</label>
<label><input type="radio" name="role" value="Technolog" onChange={e => switchRole(e.target.value)} />Technolog</label>
</div>
)
//connect radio buttons click to togling roles action
const mapDispatch = dispatch => ({switchRole: role => dispatch(setRole(role))}),
SwitchRolesContainer = connect(null,mapDispatch)(SwitchRoles)
//ui component to toggle 'approved' within global state
const ToggleApprove = ({onApprove,isManager}) => (
<button onClick={onApprove} disabled={!isManager}>Toggle</button>
)
//connect onToggle handler to dispatching 'toggle' action
const mapStateToProps = ({role}) => ({isManager: role == 'Manager'}),
mapDispatchToProps = dispatch => ({onApprove: () => dispatch(mngAppr())}),
ToggleApproveContainer = connect(mapStateToProps, mapDispatchToProps)(ToggleApprove)
//ui component to display current state of 'open'
const IsApproved = ({isApproved}) => <div>{isApproved ? 'Approved by manager' : 'Not approved by manager'}</div>
//attach isOpen prop to global 'open' variable
const mapState = ({approved}) => ({isApproved: approved}),
IsApprovedContainer = connect(mapState)(IsApproved)
//render the app
render (
<Provider store={store}>
<SwitchRolesContainer />
<IsApprovedContainer />
<ToggleApproveContainer />
</Provider>,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.3/react-redux.min.js"></script><div id="root"></div>
Hopefully, it gives a piece of mind about toggling global variables and mapping their values onto local components state.

Can't perform a React state update on an unmounted component theme provider

I need help because I get the following 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 the componentWillUnmount method. in createCategory (at themeProvider.js:39)
/* Imports */
import React, { useContext, useState, useEffect } from 'react';
import AsyncStorage from '#react-native-community/async-storage';
import THEMES from '#app/theme/themes.json';
/* /Imports/ */
const STORAGE_KEY = 'THEME_ID';
const ThemeContext = React.createContext();
/* Exports */
export const ThemeContextProvider = ({ children }) => {
const [themeID, setThemeID] = useState();
useEffect(() => {
(async () => {
const storedThemeID = await AsyncStorage.getItem(STORAGE_KEY);
if (storedThemeID) setThemeID(storedThemeID);
else setThemeID(THEMES[1].key);
})();
}, []);
return (
<ThemeContext.Provider value={{ themeID, setThemeID }}>
{!!themeID ? children : null}
</ThemeContext.Provider>
);
};
export function withTheme(Component) {
function TargetComponent(props) {
const { themeID, setThemeID } = useContext(ThemeContext);
const getTheme = themeID => THEMES.find(theme => theme.key === themeID);
const setTheme = themeID => {
AsyncStorage.setItem(STORAGE_KEY, themeID);
setThemeID(themeID);
};
return (
<Component
{...props}
themes={THEMES}
theme={getTheme(themeID)}
setTheme={setTheme}
/>
);
}
TargetComponent.navigationOptions = Component.navigationOptions;
return TargetComponent;
}
/* /Exports/ */
If you don't already know - you can return a function at the end of your useEffect hook. That function will be called whenever that effect is fired again (e.g. when the values of its dependencies have changed), as well as right before the component unmounts. So if you have a useEffect hook that looks like this:
useEffect(() => {
// logic here
return () => {
// clean up
};
}, []); // no dependencies!
Is equivalent to this:
class SomeComponent extends React.Component {
componentDidMount() {
// logic here
}
componentWillUnmount() {
// clean up
}
}
So in your code I'd add this:
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
try {
// fetch logic omitted...
const data = await AsyncStorage.getItem(STORAGE_KEY);
if (storedThemeID) setThemeID(storedThemeID);
else setThemeID(THEMES[1].key);
} catch (e) {
throw new Error(e)
}
};
fetchData();
return () => {
isCancelled = true;
};
}, [themeID]);
Try this
let unmounted = false;
useEffect(() => {
(async () => {
const storedThemeID = await AsyncStorage.getItem(STORAGE_KEY);
if (!unmounted) {
if (storedThemeID) setThemeID(storedThemeID);
else setThemeID(THEMES[1].key);
}
})();
return () => {
unmounted = true;
};
}, []);

Categories