Destructure using rest parameters - reactjs

I am trying to destructure a React context based on rest parameters passed to the hook.
Say I am passing an array of enums and want to return only the ones that are passed into the hook.
Here's my interface context type
enum ConfigItem {
SomeItem = 'SomeItem',
AnotherItem = 'AnotherItem',
}
type Config = Record<ConfigItem, boolean>;
type ConfigState = {
config: Config;
}
and the hook itself
const useConfig = (...configArgs: ConfigItem) => {
const configContext = useContext(ConfigContext);
const { config } = configContext;
const { ...configArgs } = config; // Duplicate identifier 'configArgs'.
return configArgs;
}
I would like to use it like so
const config = useConfig(ConfigItem.SomeItem, ConfigItem.AnotherItem);
and that would return an object with relevant properties. I might want to pass a single arg but potentially many.
The above const would return this (true/false is whatever would be in the context but that is not in scope of the question)
{
SomeItem: true/false,
AnotherItem: true/false,
}
but if I would only pass one of them I expect to see a single property.

By their nature, rest parameters are arrays, so you need an array type:
const useConfig = (...configArgs: ConfigItem[]) => {
// ^^−−−−−−−−−−−−−−−−−−−−−−−−−
// ...
If you pass just one argument, the array will have one element, but it will still be an array.
In a comment you've said:
My problem is how to return only the provided args properties but thanks for the correction.
If you mean you're having trouble creating the object you want to return, you can do that in a few ways; I'd probably lean toward Object.fromEntries (you'll need to polyfill if targeting pre-ES2019 environments):
const useConfig = (...configArgs: ConfigItem[]) => {
const configContext = useContext(ConfigContext);
const { config } = configContext;
return Object.fromEntries(
configArgs.map(itemKey => [itemKey, config[itemKey]])
) as Record<ConfigItem, boolean>;
};
You may need a type guard to avoid

Related

How to debug "Warning: Maximum update depth exceeded" in React [duplicate]

Is there an easy way to determine which variable in a useEffect's dependency array triggers a function re-fire?
Simply logging out each variable can be misleading, if a is a function and b is an object they may appear the same when logged but actually be different and causing useEffect fires.
For example:
React.useEffect(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d])
My current method has been removing dependency variables one by one until I notice the behavior that causes excessive useEffect calls, but there must be a better way to narrow this down.
I ended up taking a little bit from various answers to make my own hook for this. I wanted the ability to just drop something in place of useEffect for quickly debugging what dependency was triggering useEffect.
const usePrevious = (value, initialValue) => {
const ref = useRef(initialValue);
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((accum, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...accum,
[keyName]: {
before: previousDeps[index],
after: dependency
}
};
}
return accum;
}, {});
if (Object.keys(changedDeps).length) {
console.log('[use-effect-debugger] ', changedDeps);
}
useEffect(effectHook, dependencies);
};
Below are two examples. For each example, I assume that dep2 changes from 'foo' to 'bar'. Example 1 shows the output without passing dependencyNames and Example 2 shows an example with dependencyNames.
Example 1
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2])
Console output:
{
1: {
before: 'foo',
after: 'bar'
}
}
The object key '1' represents the index of the dependency that changed. Here, dep2 changed as it is the 2nd item in the dependency, or index 1.
Example 2
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2], ['dep1', 'dep2'])
Console output:
{
dep2: {
before: 'foo',
after: 'bar'
}
}
#simbathesailor/use-what-changed works like a charm!
Install with npm/yarn and --dev or --no-save
Add import:
import { useWhatChanged } from '#simbathesailor/use-what-changed';
Call it:
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]
useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
// your effect
}, deps);
Creates this nice chart in the console:
There are two common culprits:
Some Object being pass in like this:
// Being used like:
export function App() {
return <MyComponent fetchOptions={{
urlThing: '/foo',
headerThing: 'FOO-BAR'
})
}
export const MyComponent = ({fetchOptions}) => {
const [someData, setSomeData] = useState()
useEffect(() => {
window.fetch(fetchOptions).then((data) => {
setSomeData(data)
})
}, [fetchOptions])
return <div>hello {someData.firstName}</div>
}
The fix in the object case, if you can, break-out a static object outside the component render:
const fetchSomeDataOptions = {
urlThing: '/foo',
headerThing: 'FOO-BAR'
}
export function App() {
return <MyComponent fetchOptions={fetchSomeDataOptions} />
}
You can also wrap in useMemo:
export function App() {
return <MyComponent fetchOptions={
useMemo(
() => {
return {
urlThing: '/foo',
headerThing: 'FOO-BAR',
variableThing: hash(someTimestamp)
}
},
[hash, someTimestamp]
)
} />
}
The same concept applies to functions to an extent, except you can end up with stale closures.
UPDATE
After a little real-world use, I so far like the following solution which borrows some aspects of Retsam's solution:
const compareInputs = (inputKeys, oldInputs, newInputs) => {
inputKeys.forEach(key => {
const oldInput = oldInputs[key];
const newInput = newInputs[key];
if (oldInput !== newInput) {
console.log("change detected", key, "old:", oldInput, "new:", newInput);
}
});
};
const useDependenciesDebugger = inputs => {
const oldInputsRef = useRef(inputs);
const inputValuesArray = Object.values(inputs);
const inputKeysArray = Object.keys(inputs);
useMemo(() => {
const oldInputs = oldInputsRef.current;
compareInputs(inputKeysArray, oldInputs, inputs);
oldInputsRef.current = inputs;
}, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};
This can then be used by copying a dependency array literal and just changing it to be an object literal:
useDependenciesDebugger({ state1, state2 });
This allows the logging to know the names of the variables without any separate parameter for that purpose.
As far as I know, there's no really easy way to do this out of the box, but you could drop in a custom hook that keeps track of its dependencies and logs which one changed:
// Same arguments as useEffect, but with an optional string for logging purposes
const useEffectDebugger = (func, inputs, prefix = "useEffect") => {
// Using a ref to hold the inputs from the previous run (or same run for initial run
const oldInputsRef = useRef(inputs);
useEffect(() => {
// Get the old inputs
const oldInputs = oldInputsRef.current;
// Compare the old inputs to the current inputs
compareInputs(oldInputs, inputs, prefix)
// Save the current inputs
oldInputsRef.current = inputs;
// Execute wrapped effect
func()
}, inputs);
};
The compareInputs bit could look something like this:
const compareInputs = (oldInputs, newInputs, prefix) => {
// Edge-case: different array lengths
if(oldInputs.length !== newInputs.length) {
// Not helpful to compare item by item, so just output the whole array
console.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
console.log("Old inputs:", oldInputs)
console.log("New inputs:", newInputs)
return;
}
// Compare individual items
oldInputs.forEach((oldInput, index) => {
const newInput = newInputs[index];
if(oldInput !== newInput) {
console.log(`${prefix} - The input changed in position ${index}`);
console.log("Old value:", oldInput)
console.log("New value:", newInput)
}
})
}
You could use this like this:
useEffectDebugger(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d], 'Effect Name')
And you would get output like:
Effect Name - The input changed in position 2
Old value: "Previous value"
New value: "New value"
There’s another stack overflow thread stating you can use useRef to see a previous value.
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
The React beta docs suggest these steps:
Log your dependency array with console.log:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
Right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. It may be important not to compare two sequential ones if you are in strict mode, I'm not sure.
Compare each of the dependencies:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
This question was answered with several good and working answers, but I just didn't like the DX of any of them.
so I wrote a library which logs the dependencies that changed in the easiest way to use + added a function to log a deep comparison between 2 objects, so you can know what exactly changed inside your object.
I called it: react-what-changed
The readme has all of the examples you need.
The usage is very straight forward:
npm install react-what-changed --save-dev
import { reactWhatChanged as RWC } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, RWC([somePrimitive, someArray, someObject]));
}
In this package you will also find 2 useful functions for printing deep comparison (diffs only) between objects. for example:
import { reactWhatDiff as RWD } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, [somePrimitive, someArray, someObject]);
RWD(someArray);
}

optional question mark and object destruction

const { isSaved } = props?.options;
does this code make sense? what's the purpose of using ? here?
is it better if I do
const { isSaved = null } = props.options;
The first code does not make sense. With optional chaining, when any part of the chain fails, the whole expression will evaluate to undefined. But you can't access properties of undefined, so if the chain fails, the engine will throw:
const props = undefined;
const { isSaved } = props?.options;
But in React, props, if you're referring to what's normally referred to as props, will always be truthy. At worst, it'll be an empty object.
Your second code makes more sense, but it will still throw if options happens to not be a prop:
const props = {};
const { isSaved = null } = props.options;
Alternate with the empty object instead:
const props = {};
const { isSaved = null } = props.options || {};
console.log(isSaved);

How to access the functional component's name from a React hook?

I am trying to write a custom React hook, useLogging, where I would like to contextualize the log message based upon the name of the component which is doing the logging.
For example:
const Login: React.FunctionComponent<IProps> = (props) => {
log = useLogging();
log.info("Hello!")
[...]
Should produce [Login] Hello!
My custom hook, then, needs the name Login:
export const useLogger = () => {
// "this" is undefined
const loggerName = ??????
return logManager.getLogger(loggerName);
};
In the context of a class, what I'm looking for is something like this.constructor.displayName. However, a React hook does not have this set, and I cannot seem to find documentation on obtaining reference to the functional component's context.
—-
Edit: I would prefer not to pass any arguments and not to add a bunch of boiler plate. My goal is that the useLogging() function will survive component refactoring, and not rely upon the developer to provide a "correct" name.
There's a couple other ways that I can think of that you could use. The first and the simplest the one Drew suggested in the comment, which is to simply pass the logging name as an argument:
const useLogger = (name: string) => {
return logManager.getLogger(name)
}
const Login: React.FC<Props> = () => {
const log = useLogger('Login')
// ...
}
You could also obtain the name via .displayName or .name. Notice that .name refers to Function.name, which, if you're using webpack, will probably be minified in a production build so you'll end up with names like "t" or "s" etc. If you need the same name as in your component, you can assign displayName and let the hook take care of it:
const useLogger = (component: React.ComponentType<any>) => {
const name = useLogger(component.displayName || component.name);
return logManager.getLogger(name);
}
const Login: React.FC<Props> = () => {
const log = useLogger(Login)
}
Login.displayName = 'Login';
If you're okay with passing a name to useLogger, but don't want to set displayName every time, you could use something like ts-nameof which aims to give you a nameof operator like in C#:
const useLogger = (name: string) => {
return logManager.getLogger(name)
}
const Login: React.FC<Props> = () => {
const log = useLogger(nameof(Login))
// ...
}
The upside here is that the name will survive auto-renames. This requires some bundler or Babel configuration. I haven't tested how minification affects this, but there's three different flavors of ts-nameof (at the time of writing) which you can use:
a compile-time transform for the TypeScript compiler: ts-nameof
a Babel macro: ts-nameof.macro
a Babel plugin: babel-plugin-ts-nameof
Pick the first one that matches your build pipeline.
Alternatively, if the logger isn't component-specific, but module-specific, you could make a factory for the hook, and initialize it once at the top of your module:
const makeUseLogger = (name: string) => () => {
return logManager.getLogger(name)
}
// in your module
const useLogger = makeUseLogger('Module name')
const Login: React.FC<Props> = () => {
const log = useLogger()
// ...
}
As an extension of this, if the logger itself doesn't actually need to be a hook (doesn't use other hooks or need props etc.), just make a logger for your module at the top level directly:
const log = logManager.getLogger('Module name')
const Login: React.FC<Props> = () => {
log.info('hello')
}
Additionally, if you don't mind your project's directory structure leaking into a production build, you can use a webpack trick:
// webpack.config.js
module.exports = {
// ...
node: {
__filename: true
}
}
and then
const log = logManager.getLogger(__filename)
In a file whose path is /home/user/project/src/components/Login.ts, and the webpack context is /home/user/project, the __filename variable will resolve to be src/components/Login.ts.
Although, this will probably require you to create a typedef e.g. globals.d.ts where you declare the __filename global for Typescript:
declare global {
__filename: string;
}
Note: this will not work if your build target is umd.
As a side-note, technically, if you for some reason don't want to pass any args to useLogging, you could use the deprecated Function.caller property, e.g.
function useLogging() {
const caller = (useLogging.caller as React.ComponentType<any>);
const name = caller.displayName || caller.name;
console.log(name);
return logManager.getLogger(name);
}
const Login: React.FC<Props> = () => {
const log = useLogging()
// ...
}
However, that property is deprecated so you'll have to clean that up sooner or later, so don't do that in production code.

Can someone explain how input functions are used in functions in reselect library?

https://github.com/reduxjs/reselect/blob/master/src/index.js#L89
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
export function createSelectorCreator(memoize, ...memoizeOptions) {
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
...}
}
export const createSelector = createSelectorCreator(defaultMemoize)
So if I create createSelector(getUsers, (users) => users) for making a simple example. How is it run behind with the codes from above ?
createSelectorCreator(defaultMemoize) is called with getUsers, (users) => users inputs. Now defaultMemoize is also a function that returns a function. How are they all interacting to return the value ?
I think more important to how reselect works is why one should use it. The main reasons are composability and memomization:
Composability
Another way of saying this is that you write a selector once and re use it in other more detailed selectors. Lets say I have a state like this: {data:{people:[person,person ...]} Then I can write a filterPerson like this:
const selectData = state => state.data;
const selectDataEntity = createSelector(
selectData,//re use selectData
(_, entity) => entity,
(data, entity) => data[entity]
);
const filterDataEntity = createSelector(
selectDataEntity,//re use selectDataEntity
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
If I move data to state.apiResult then I only need to change selectData, this maximizes re use of code and minimizes duplication of implementation.
Memoization
Memoization means that when you call a function with the same arguments multiple times the function will only be executed once. Pure functions return the same result given the same arguments no matter how many times they are called or when they are called.
This means that you don't need to execute the function when you call it again with the same parameters because you already know the result.
Memoization can be used to not call expensive functions (like filtering a large array). In React memoization is important because pure components will re render if props change:
const mapStateToProps = state => {
//already assuming where data is and people is not
// a constant
return {
USPeople: state.data.people.filter(person=>person.address.countey === US)
}
}
Even if state.data.people didn't change the filter function would return a new array every time.
How it works
Below is a re write of createSelector with some comments. Removed some code that would safety check parameters and allow you to call createSelector with an array of functions. Please comment if there is anything you have difficulty understanding.
const memoize = fn => {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
};
const createSelector = (...functions) => {
//get the last function by popping it off of functions
// this mutates functions so functions does not have the
// last function on it anymore
// also memoize the last function
const lastFunction = memoize(functions.pop());
//return a selector function
return (...args) => {
//execute all the functions (last was already removed)
const argsForLastFunction = functions.map(fn =>
fn.apply(null, args)
);
//return the result of a call to lastFunction with the
// result of the other functions as arguments
return lastFunction.apply(null, argsForLastFunction);
};
};
//selector to get data from state
const selectData = state => state.data;
//select a particular entity from state data
// has 2 arguments: state and entity where entity
// is a string (like 'people')
const selectDataEntity = createSelector(
selectData,
(_, entity) => entity,
(data, entity) => data[entity]
);
//select an entity from state data and filter it
// has 3 arguments: state, entity and filterFunction
// entity is string (like 'people') filter is a function like:
// person=>person.address.country === US
const filterDataEntity = createSelector(
selectDataEntity,
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
//some constants
const US = 'US';
const PEOPLE = 'people';
//the state (like state from redux in connect or useSelector)
const state = {
data: {
people: [
{ address: { country: 'US' } },
{ address: { country: 'CA' } },
],
},
};
//the filter function to get people from the US
const filterPeopleUS = person =>
person.address.country === US;
//get people from the US first time
const peopleInUS1 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
//get people from the US second time
const peopleInUS2 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
console.log('people in the US:', peopleInUS1);
console.log(
'first and second time is same:',
peopleInUS1 === peopleInUS2
);

How can I desctructure items out of an object that is returned from a React hook using Typescript?

I have a component that needs to tap into the React Router query params, and I am using the use-react-router hook package to access them.
Here is what I am wanting to do:
import React from "react;
import useReactRouter from "use-react-router";
const Foo = () => {
const { id } = useReactRouter().match.params;
return (
<Bar id={id}/>
)
}
The issue is that this throws the following error in VS Code, and at compile time:
Property 'id' does not exist on type '{}'.ts(2339)
I have found that if I refactor my code like so:
const id = match.params["id"], I do not get the error, but I feel like this is not the correct approach for some reason. If someone could point me in the right direction, I would appreciate it.
I figured it out. The solution was to include angle brackets between the hook's name and the parenthesis, like so:
const { match } = useRouter<{ id: string }>();
const { id } = useRouter<{ id: string }>();
Or if you prefer nested destructuring:
const { match: { params: id } } = useRouter<{ id: string }>();
You can try to give default value to params
const { id } = useReactRouter().match.params || {id: ""};
It may be possible that params to be null at initial level
The code is insufficient.
However, at first glance,
// right way
const { history, location, match } = useReactRouter()
// in your case
const { match: { params : { id } } } = useReactRouter()
// or
const { match } = useReactRouter()
const { id } = match.params
now, try to console the value first.
Also, please try to pass the props to a functional component from it's container, since it's more logical.
From your comment below, i can only assume you solved it. Also, it's recommended to handle possible undefined values when you use it.
{ id && id }
However, the first step should've been consoling whether it has value in it,
console.log('value xyz', useReactRouter())

Resources