Access React Context via function - javascript

I am currently developing a package, which gives my React-widget responsiveness. The problem is, that the responsiveness does not depends on the viewport-width but on on the width of the widget-container-element.
Currently I am wrapping my <App> with a <ResponsiveProvider>. This provider subscribes to the windows.resize event and stores the format into the context's value.
All children elements get re-rendered if the format changes. That's fine.
Now, for show/hide components based on the current widget format, I just could implement a component, which accesses this context with contextType.
But I need a function, which I can use in any place of my application like: ResponsiveUtil.isSmall() or ResponsiveUtil.getCurrentFormat().
What would be the best approach to make the information (format) accessable via a function?
Thanks

I'm not sure if this would be the best approach, but it will work. You can set up a global event listener that will be dispatched each time the format changes in your component. I found a package here for the global events, but it wouldn't be hard to write your own either. Using react-native-event-listeners, it would look something like:
ResponsiveUtil.js
import { EventRegister } from 'react-native-event-listeners';
let format = {};
EventRegister.addEventListener('responsive-format-changed', data => {
format = data;
});
const ResponsiveUtils = {
getCurrentFormat() {
return Object.assign({}, format);
},
isSmall() {
//isSmall logic
}
};
export default ResponsiveUtils;
Then, in your <ResponsiveProvider>, during the resize event, dispatch the new format when you update the context
ResponsiveProvider.js
import { EventRegister } from 'react-native-event-listeners';
//...Other component code
window.resize = () => {
let format = calculateNewFormat();
//update context...
//dispatch new format
EventRegister.emit('responsive-format-changed', format);
};

Related

Am I using React hooks wrong?

I wanted to create a sevice-like hook that doesn't hold state, it just exports an object with funtions.
I first started with this:
export default useFoo = () => ({ // some functions here... });
But then I realized that this wouldn't be the best approach because a new object is going to be created every time the hook is called and I don't want that - I need one global object with the same reference across all components, so then I tried this:
const foo = { // some functions here... };
export default useFoo = () => foo;
It works as expected, but I'm not sure if it's the right way to do it. Is there a better way to achieve this? Or should I use context maybe?
EDIT: I know that I can just export a plain JS object and not bother myself with hooks, but I need it to be a hook because I use other hooks inside.
It works as expected, but I'm not sure if it's the right way to do it. Is there a better way to achieve this?
If foo never changes, and doesn't need to close over any values from the other hooks you're calling in useFoo, then that's fine. If it does need to change based on other values, then you can use useCallback and/or useMemo to only recreate the object when relevant things change.
export default useFoo = () => {
const something = useSomeHook();
const foo = useMemo(() => {
return { /* some functions that use `something` */ }
}, [something]);
return foo;
}

Can't find the way to useSelector from redux-toolkit within event handler and pass params to it

There is an event handler for click and when it triggered i want to pull specific data from redux using selector where all logic many-to-many is implemented. I need to pass id to it in order to receive its individual data. Based on rules of the react the hooks can be called in function that is neither a React function component nor a custom React Hook function.
So what is the way to solve my problem ?
const handleMediaItemClick = (media: any): void => {
// For example i check media type and use this selector to pull redux data by id
const data = useSelector(playlistWithMediaSelector(imedia.id));
};
As stated in the error message, you cannot call hooks inside functions. You call a hook inside a functional component and use that value inside the function. The useSelector hook updates the variable each time the state changes and renders that component.
Also, when you get data with useSelector, you should write the reducer name you need from the redux state.
const CustomComponent = () => {
// The data will be updated on each state change and the component will be rendered
const data = useSelector((state) => state.REDUCER_NAME);
const handleMediaItemClick = () => {
console.log(data);
};
}
You can check this page for more information.https://react-redux.js.org/api/hooks#useselector
You should probably use local state value to track that.
const Component = () => {
const [imediaId, setImediaId] = useState(null);
const data = useSelector(playlistWithMediaSelector(imediaId));
function handleMediaClick(id) {
setImediaId(id)
}
useEffect(() => {
// do something on data
}, [imediaId, data])
return <div>...</div>
}
Does that help?
EDIT: I gather that what you want to do is to be able to call the selector where you need. Something like (considering the code above) data(id) in handleMediaClick. I'd bet you gotta return a curried function from useSelector, rather than value. Then you would call it. Alas, I haven't figured out how to that, if it's at all possible and whether it's an acceptable pattern or not.

Is it possible to use references as react component's prop or state?

I want to create react table component which values are derived from single array object. Is it possible to control the component from view side? My goal is that every user using this component in their web browsers share the same data via singleton view object.
Program modeling is like below.
Database - there are single database in server which contain extinct and independent values.
DataView - there are singleton View class which reflects Database's table and additional dependent data like (sum, average)
Table - I'll build react component which looks like table. And it will show View's data with supporting sorting, filtering, editing and deleting row(s) feature (and more). Also it dose not have actual data, only have reference of data from View(Via shallow copy -- This is my question, is it possible?)
My intentions are,
- When user changes value from table, it is queried to DB by View, and if succeed, View will refer updated data and change it's value to new value and notify to Table to redraw it's contents. -- I mean redraw, not updating value and redraw.
- When values in View are changed with DB interaction by user request, there are no need to update component's value cause the components actually dose not have values, only have references to values (Like C's pointer). So only View should do is just say to Component to redraw it's contents.
I heard that React's component prop should be immutable. (Otherwise, state is mutable) My goal is storing references to component's real value to it's props so that there are no additional operation for reflecting View's data into Table.
It is concept problems, and I wonder if it is possible. Since javascript dose not support pointer officially(Am I right?), I'm not sure if it is possible.
View class is like below,
const db_pool = require('instantiated-singleton-db-pool-interface')
class DataView {
constructor() {
this.sessions = ['user1', 'user2'] // Managing current user who see the table
this.data = [ // This is View's data
{id:1, name:'James', phone:'12345678', bank:2000, cash:300, total:2300,..},
{id:2, name:'Michael', phone:'56785678', bank:2500, cash:250, total:2300,..},
{id:3, name:'Tyson', phone:'23455432', bank:2000, cash:50, total:2300,..}
] // Note that 'total' is not in db, it is calculated --`dependent data`
}
notifySessionToUpdate(ids) {
// ids : list of data id need to be updated
this.sessions.forEach((session) => {
session.onNotifiedUpdateRow(ids) // Call each sessions's
})
}
requestUpdateRow(row, changed_value) {
// I didn't write async, exception related code in this function for simple to see.
update_result = db_pool.update('UPDATE myTable set bank=2500 where id=1')
if (update_result === 'fail') return; // Do Nothing
select_result = db_pool.select('SELECT * from myTable where id=1') // Retrieve updated single data which object scheme is identical with this.data's data
for (k in Object.keys(select_result)) {.ASSIGN_TO_row_IF_VALUE_ARE_DIFFERENT.} // I'm not sure if it is possible in shallow copy way either.
calc.reCalculateRow(row) // Return nothing just recalculate dependant value in this.data which is updated right above.
// Notify to session
this.notifySessionToUpdate([1]) // Each component will update table if user are singing id=1's data if not seeing, it will not. [1] means id:1 data.
return // Success
}
... // other View features
}
Regarding session part, I'm checking how to implement sessionizing(?) the each user and it's component who is communicating with server. So I cannot provide further codes about that. Sorry. I'm considering implementing another shallow copied UserView between React Component Table and DataView(And also I think it helps to do something with user contents infos like sorting preference and etc...)
Regarding DB code, it is class which nest it's pool and query interface.
My problem is that I'm not familiar with javascript. So I'm not sure shallow copy is actually implementable in all cases which I confront with.
I need to think about,
1. Dose javascript fully support shallowcopy in consistent way? I mean like pointer, guarantee check value is reference or not.
2. Dose react's component can be used like this way? Whether using props or state Can this be fullfilled?
Actually, I strongly it is not possible to do that. But I want to check your opinions. Seems it is so C language-like way of thinking.
Redraw mean re-render. You can expose setState() or dispatch() functions from Table component and call them on View level using refs:
function View() {
const ref = useRef();
const onDbResponse = data => ref.current.update(data);
return (
<Table ref={ ref } />
);
}
const Table = React.forwardRef((props, ref) => {
const [ data, setData ] = useState([]);
useImperativeHandler(ref, {
update: setData
});
...
});
Anyway i don't think it's a good practice to update like that. Why can't you just put your data in some global context and use there?
const Context = React.createContext({ value: null, query: () => {} });
const Provider = ({ children }) => {
const [ value, setValue ] = useState();
const query = useCallback(async (request) => {
setValue(await DB.request(request));
}, [ DB ]);
const context = { value, query };
return <Context.Provider value={ context }>{ children }</Context.Provider>;
}
const useDB = () => useContext(Context);
const View = () => {
const { request } = useDB();
request(...);
}
const Table = () => {
const { value } = useDB();
...
}

How to get changes in Vue updated hook?

If I have a Vue component like:
<script>
export default {
updated() {
// do something here...
}
};
</script>
is there anyway to get the changes that resulted in the update? Like how watch hooks accept arguments for previous and next data?
watch: {
someProp(next, prev) {
// you can compare states here
}
}
React seems to do this in componentDidUpdate hooks, so I'm assuming Vue has something similar but I could be wrong.
The updated lifecycle hook doesn't provide any information on what caused the Vue component instance to be updated. The best way to react to data changes is by using watchers.
However, if you're trying to investigate what caused an update for debugging purposes, you can store a reference to the state of the Vue instance's data and compare it to the state when updated.
Here's an example script using lodash to log the name of the property that changed, triggering the update:
updated() {
if (!this._priorState) {
this._priorState = this.$options.data();
}
let self = this;
let changedProp = _.findKey(this._data, (val, key) => {
return !_.isEqual(val, self._priorState[key]);
});
this._priorState = {...this._data};
console.log(changedProp);
},
This works because properties prepended with the underscore character are reserved for internal use and are not available for binding. This could be saved in a mixin to use whenever you needed to debug a Vue component this way.
Here's a working fiddle for that example.

Window Resize - React + Redux

I'm new to Redux and I'm wondering if anyone has some tips on best practices for handling non React events like window resize. In my research, I found this link from the official React documentation:
https://facebook.github.io/react/tips/dom-event-listeners.html
My questions is, when using Redux, should I store the window size in my Store or should I be keeping it in my individual component state?
Good question. I like to to have a ui part to my store. The reducer for which might look like this:
const initialState = {
screenWidth: typeof window === 'object' ? window.innerWidth : null
};
function uiReducer(state = initialState, action) {
switch (action.type) {
case SCREEN_RESIZE:
return Object.assign({}, state, {
screenWidth: action.screenWidth
});
}
return state;
}
The action for which is pretty boilerplate. (SCREEN_RESIZE being a constant string.)
function screenResize(width) {
return {
type: SCREEN_RESIZE,
screenWidth: width
};
}
Finally you wire it together with an event listener. I would put the following code in the place where you initialise your store variable.
window.addEventListener('resize', () => {
store.dispatch(screenResize(window.innerWidth));
});
Media Queries
If your app takes a more binary view of screen size (e.g. large/small), you might prefer to use a media query instead. e.g.
const mediaQuery = window.matchMedia('(min-width: 650px)');
if (mediaQuery.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
mediaQuery.addListener((mq) => {
if (mq.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
});
(I'll leave out the action and reducer code this time. It's fairly obvious what they look like.)
One drawback of this approach is that the store may be initialised with the wrong value, and we're relying on the media query to set the correct value after the store has been initialised. Short of shoving the media query into the reducer file itself, I don't know the best way around this. Feedback welcome.
UPDATE
Now that I think about it, you can probably get around this by doing something like the following. (But beware, I have not tested this.)
const mediaQuery = window.matchMedia('(min-width: 650px)');
const store = createStore(reducer, {
ui: {
largeScreen: mediaQuery.matches
}
});
mediaQuery.addListener((mq) => {
if (mq.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
});
UPDATE II: The drawback of this last approach is that the ui object will replace the entire ui state not just the largeScreen field. Whatever else there is of the initial ui state gets lost.
Use redux-responsive to handle the responsive state of your application. It uses a store enhancer to manage a dedicated area(property) of your store's state (normally called 'browser') via its own reducer, so that you don't have to implicitly add event listeners to the document object.
All you need to do is to map the browser.width, browser.height, etc. to your component's props.
Please note that only the reducer defined in redux-responsive is responsible for updating these values.
I have a similar case where I need the window size for purposes other than responsiveness. According to this, you could also use redux-thunk:
function listenToWindowEvent(name, mapEventToAction, filter = (e) => true) {
return function (dispatch) {
function handleEvent(e) {
if (filter(e)) {
dispatch(mapEventToAction(e));
}
}
window.addEventListener(name, handleEvent);
// note: returns a function to unsubscribe
return () => window.removeEventListener(name, handleEvent);
};
}
// turns DOM event into action,
// you can define many of those
function globalKeyPress(e) {
return {
type: 'GLOBAL_KEY_PRESS',
key: e.key
};
}
// subscribe to event
let unlistenKeyPress = store.dispatch(listenToWindowEvent('keypress', globalKeyPress));
// eventually unsubscribe
unlistenKeyPress();
Although in reality, if your use case is a simple one you don't even need to use a thunk function. Simply create a listener function that takes Redux dispatch as a parameter and use it to dispatch desired action. See the reference for an example. But the currently accepted answer pretty much covers this case

Categories