I have two functional components. The only difference between them is that one is named. Like this:
const one = (props) => (<div>i am the first one</div>);
switch (type) {
case 1:
return one;
case 2:
return (props) => (<div>i am the second one</div>);
}
So the problem is the behavior is not the same. When the named component just rerenders itself after props change, the anonymous component recreates itself (call componentDidMount and componentDidUnmount). Could anyone explain what's happening there?
Named component is created once, and then you use a reference to it in a switch or somewhere else.
Unnamed function is created every time you call it.
Javascript functions are objects.
The object never equals another object except for the reference to itself.
Since the previous unnamed function doesn't equal the new one, React remounts it.
Related
I am learning Redux Toolkit. From a React POV it seems very intuitive to access whatever part of the state you need from within useSelector using an inline arrow function, and then conduct any calculations. As an example consider a cart item with its data (like item count) in redux store.
function CartItemCounter({ itemId }){
const cart = useSelector(state => state.cart);
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
return <div>{itemId} - {count} nos</div>
}
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all. What is the right way, and why is it better?
The information that is out there is essentially talking about different levels of optimization while using useSelector.
What you need to understand before anything else, is how useSelector works internally.
How does useSelector work?
When you pass a function to useSelector (obviously inside a react component), it essentially hooks on to the global redux state. Whenever any change happens in any part of the global state (i.e. when dispatch() is called from any part of the app), redux will run all the functions you passed to useSelector in your app, and perform certain checks.
Redux will take the result from each function, and compare it to the value it got the last time it ran the same function.
How does it make this comparison?
It uses a reference equality for this comparison. So if redux has to think that the result of the function hasn't changed, either the value returned from the function has to be a primitive and equal.
4 === 4 // true
'itemA' === 'itemA // true
Or, the value returned must be a derived data type (arrays, objects), with the same reference. So essentially the same object.
const x = { name: 'Shashi' }
const fn1 = () => x;
const fn2 = () => x;
const fn3 = () => { name: 'Shashi' }
fn1() === fn2(); // true
fn1() === fn3(); // false, because the objects are different, with different references
In practice, redux changes the wrapping object if either a key (or key of a nested object) is changed, or you manually change the entire object using a dispatch action (This is related to the immer library integration). This is similar to how you would do in regular React.
/* See how most keys are spread in, and will hence maintain reference equality.
While certain keys like 'first', 'first.second', 'first.second[action.someId]'
are changed with new objects, and so will break reference equality */
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
Otherwise it maintains the same objects within its state, and returns the exact same objects with the same references, when you access them. To verify this, if you access your cart twice, its literally going to be the same object.
const cart1 = useSelector(state => state.cart)
const cart2 = useSelector(state => state.cart)
cart1 === cart2; // true
What does it do with this comparison?
If the comparison returns true, i.e. the new value is the same as the old value, Redux tells that instance of useSelector to chill tf out, and not do anything. If it returns false however, it tells that component to re-render. After all, the value you are accessing from state has "changed"(according to Redux laws), so you probably want to show the new value.
With this information, we can make change the kind of function we pass to the useSelector, in order to get certain optimization benefits.
Optimization Levels
Level 0: Accessing slice data inline
const cart = useSelector(state => state.cart)
// extract the information you need from within the cart
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
This is not a good way to access the data. You actually need a subset of the data from the cart, but you are fetching the whole thing, and doing the calculation outside the selector.
Problems:
When you put stuff like this inline, what happens if you change the shape of your data in the future? You have to go to every place that uses useSelector and manually change it. Not so good.
More importantly, every time any part of the cart changes, the entire cart object actually changes. So Redux sees your component that asks for the cart, and thinks
The cart has changed. This component is asking for the cart. It should probably re-render.
BAM Every single instance of this component rerenders. And for what? The count of the item you're referencing probably didn't change. So ideally there shouldn't have been a re-render.
Level 1: Centralize the selector
An easy optimization is to put the selector function in a centralized location next to your slice. That way, if your data shape changes in the future, you can just change it in one place, and your whole app (wherever it uses that data) will work with the new shape.
// inside or next to the slice file
const selectCart = (state) => state.cart
//...
// somewhere inside a react component
const cart = useSelector(selectCart)
Level 2: Access the relevant data
Since redux is comparing the results of your selector function, if you want to avoid unnecessary rerenders, you want to make sure the results have reference equality (===). So target the exact value you wish to look at, in your selector.
// extract the information you need from within the cart, *within the selector*
const count = useSelector(state => state.cart.items[itemId]?.count || 0)
// You don't have to use a one-liner, a multi-line function is better for readability
When Redux executes these functions, it keeps a record of the value returned from these selector functions, for each individual useSelector. This time the values are going to be the same for every single counter, except the one that actually changed. All those other counters that didn't actually change in value don't have to unnecessarily re-render anymore!
And if any of you folks think this is premature optimization, the answer's no. This is more along the lines of putting a dependancy array on your useEffects to avoid infinite loops.
Not forgetting the Level 1 optimization, we can also extract this function centrally
const selectItemById = (state, itemId) => (state.cart.items[itemId]?.count || 0);
function CartItemCounter({ itemId }){
//...
// somewhere inside a react component
const count = useSelector((state) => selectCart(state, itemId))
//...
}
So that solves all of our problems right?
For now, yes. But what if this selector function has to run some expensive computation.
const selectSomething = (state) => reallyExpensiveFn(state.cart)
//...
// somewhere inside a react component
const cart = useSelector(selectSomething)
You don't want to keep running that do you?
Or what if you have no option but to return new objects from your select function. A common scenario for this case would be returning a subset of data from the state.
const selectFilteredItems = (state) => state.itemsArray.filter(checkCondition) // the filter method will always return a new array
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders every time
To solve this you would have to memoize or cache the results from the function call. Essentially you would need to make sure that if the input arguments are the same, the result will maintain reference equality with the previous result. This introduces the need to maintain some kind of cache state.
Level 3: createSelector
Fortunately, Reselect library, which is reexported with Redux Tookit, does this work for you. You can take a look at the redux toolkit for the syntax.
const selectFilteredItems = createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders only when needed
Here the second function is called the transformation function, and is where we put the expensive computation, or the function that returns inconsistent references as a result (filter,map etc).
The createSelector caches
a) the arguments to the transformation function
b) the result of the transformation function
of the previous call to the selectFilteredItems function. If the arguments are the same, it skips executing the transformation function, and gives you the result you got the last time it was executed.
So when useSelector looks at the result, it gets reference equality. Hence the re-render is skipped!
One little caveat here is that createSelector only caches the very previous result. This makes sense if you think about a single component. In a single component you are only concerned about differences in values and results compared to the previous render. But in practice, you are likely to share selectors across multiple components. If this happens, you have a single cache location, and multiple components using this cache. i.e. Stuff breaks.
Level 4: createSelector factory function
Since the logic for your selector is the same, what you need to do is run createSelector for each component that uses it. This creates a cache for each component, giving us the desired behaviour. In order to do this, we use a factory function.
const makeSelectFilteredItems = () => createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const selectFilteredItems = useMemo(makeSelectFilteredItems,[]); // make a new selector for each component, when it mounts
const cart = useSelector(selectFilteredItems) // re-renders only when needed
You intend to make a new selector (and by extension, cache) for each new component that mounts. So you put it inside the actual component function and not on the module scope. But this will re-run makeFilteredSelector for each render, and hence create a new selector for each render, and hence eliminate the cache. This is why you need to wrap the function in a useMemo with an empty dependency array. It runs on every mount.
And voila!
You now know where, why and how to use selectors in Redux. I personally feel that the createSelector syntaxes are slightly contrived. There is some discussion on changing cache sizes going on. But for now I feel that sticking to the docs should get you through most situations.
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all.
That's the way to go if you're deriving something from the state, which ends up being an expensive computation or something that's reused often throughout your app. Imagine, for example, your state.cart can contain 50.000 items and you need to sort them from most expensive item to least expensive. You don't want to re-calculate this all the time because it slows your app down. So you cache/memoize the result.
What is the right way, and why is it better?
The right way is to use memoization helpers like createSelector when/if you want to avoid expensive computation. Most people optimize prematurely, so I'd just stick to useSelector and keep it simple if in doubt.
I have a context called SortContext. What I would like to do is initialize this context (create its provider) and then use that context in the same component. Is this possible?
For example:
export default function MyComponent ({children}) {
const mySortValue = useContext(SortContext)
return (
<SortContext.Provider value={'exampleValue'}>
{children}
</SortContext.Provider>
)
}
In this component, the variable mySortContext will not have access to the value 'exampleValue', as this context is not created until after the useContext hook.
Alternatively:
export default function MyComponent ({children}) {
return (
<SortContext.Provider value={'exampleValue'}>
<SortContext.Consumer>
{context => {
const mySortValue = useContext(SortContext)
return children
}}
</SortContext.Consumer>
</SortContext.Provider>
)
}
Something like this doesnt work, as the function cannot use react hooks.
I could obviously just create a new component, put it within the <SortContext.Provider> tags, and access the context there, but is there any way to do it all in one component?
//Sidenote
To give some background on why I want this, I use the context to establish some information about how data should be sorted. I would like any components within the context to have access to this data. In the case where I want a simple button, list of data, and sort function in a single component, it seems like overkill to create two components; one to feed it the sort context, the other to host the sort buttons and data.
This is not possible without creating another component.
This is mentioned in the "Pitfall" box in the react js beta docs here (scroll down a bit) :
https://beta.reactjs.org/apis/usecontext#passing-data-deeply-into-the-tree
What I would like to do is initialize this context (create its provider) and then use that context in the same component. Is this possible?
In a sense, yes, but you'd do it through local variables, not context.
You're already writing code to generate the value that you'll pass into the provider. In your example, that's just value={'exampleValue'}, but even in a more complicated realworld case, it's entirely under your control what value you pass in. Simply save that value in a local variable, and use it anywhere else you need it.
export default function MyComponent ({children}) {
// Replace the following with whatever complicated logic you need to generate the value
const mySortValue = 'exampleValue';
// Do stuff with mySortValue here
return (
<SortContext.Provider value={mySortValue}>
{children}
</SortContext.Provider>
)
}
Currently I have a two component set up, where the parent renders some data and handles retrieval and the child is a filter. This filter allows the user to filter by status or keyword. Nothing fancy.
Now this is a paginated system. After the parent makes an initial request for data, they're given the next page ID to request if they want more. But if the filter is updated, this next page ID needs to be wiped out, as it's no good.
So what I do is pass a function from the parent to the child called updateFilter(). If the filter component has an update in state, it calls up to the parent and runs updateFilter(). One of the values updated is included in a useEffect() dependency array, so the parent then requests the new data with the new filters. Easy.
The problem is in setting up the child's useEffect(). Eslint tells me I need to add props.updateFilter to the dependency array, and while I can just ignore this, I feel that it would be wrong to do so. But the parent has a fair bit of state that will update, and when it does, it passes a new copy of updateFilter() down into the child which causes it to incorrectly trigger.
How do I go about fixing this? Can I tell the child to only use a static version of this function somehow? Or do I just exclude props.updateFilter from the dependency array? Below is a rough psuedo code of my components.
Parent {
const [stateVal, setStateVal] = useState(...);
function updateFilter(filterStatus) {
...
setStateVal(filterStatus);
}
useEffect(() => ..., [stateVal]);
return <Child updateFilter={updateFilter} />
}
Child {
const [filterStatus, setStatus] = useState(...);
useEffect(() => {
props.updateFilter(filterStatus);
}, [filterStatus] // Adding `props` here is what I think I should do, but that causes the issue. Apparently the `props` val changes every time Parent's state changes
return ( ... );
}
How do I go about fixing this? Can I tell the child to only use a
static version of this function somehow?
Yep!
So, if props.updateFilter is included in the dependency array, you have issues. You call that function and it causes your parent component to re-render. And guess what? The parent creates a new updateFilter function (it does the same thing, but it makes a new one, the reference to the function is a new value which is all React checks). This causes the child to re-render, which causes your useEffect to run because its dependency is a new function. That's bad!
So... add useCallback
function updateFilter = useCallback((filterStatus) => {
...
setStateVal(filterStatus);
}, []);
useCallback creates the function one time, and only makes a new reference if its dependencies change (as it should). It has a little bit more overhead, but if I'm ever unsure I use it.
Also, bonus, after dealing with these issues, I use the setState(previousValue => previousValue + 1) form much more than setState(previousValue + 1) as it has many benefits. previousValue doesn't have to be in the dependency array and multiple setStates can be stacked in one render cycle (instead of using the initial value).
I wrote a custom form component called SingleFilterContainer
Within the parent component called FiltersContainer, I initialize a state filters which has an array of a single filter initially and respective setFilters function to modify the array of filters. And also has a button to add a filter. And in the render function I use filters.map to render SingleFilterContainer component multiple times. So far this works. But I want to add a delete filter button. I put this inside the SingleFilterContainer. When I click this it should update the state filters in the parent component and delete the ith filter and render the rest of the filters normally. Or render the whole map of filters again.
I'm new to react and hooks, and I feel like I'm missing something. I'm at it for the past three days and I'm a little bit lost.
Here is the sandbox https://codesandbox.io/s/editing-filters-j2qrp
I feel like how I'm handling state is completely wrong. And maybe I should use redux? But I want the SingleFilterContainer to be like an ephemeral form. Or should the delete filter button be within the parent component? and repeat it using map?
TL;DR Fixed fork
The problem in your code is that you call the function on render onClick={handleDeleteFilter(i)} the onClick expects a reference to a function, but if you want this code to work, then the method that you are passing from the parent to the child needs to return a function also.
Then your handleDeleteFilter will look like this:
function handleDeleteFilter(i) {
return function() {
filters.splice(i, i + 1);
setFilters(filters => filters);
}
}
Also in your case you don't need to pass i + 1 into splice as the second argument, as the second argument is the amount of items to remove. Which in your case is just 1. Docs
You pass the i to the first function and the second one will see it due to closure.
And then to the removing of an element.
Mutating the state is bad practice, so you can have a local variable with a copy of the state, which you can manipulate and then update the state. That way you can use whatever you want, but on the local variable.
So your updated handleDeleteFilter would look like
function handleDeleteFilter(i) {
return function() {
const clone = [...filters]
clone.splice(i, 1);
setFilters(clone);
}
}
or with .filter
function handleDeleteFilter(i) {
return function() {
const clone = filters.filter((item, index) => index !== i)
setFilters(clone);
}
}
this way you don't need a new variable as .filter returns a new array. Docs
Here's my fork.
The following is my changes.
function handleDeleteFilter(i) {
setFilters(filters => filters.filter((e, index) => index !== i));
}
Important notes:
Ideally, you need to assign an id property for each item in the array instead of comparing by index.
Don't directly mutate the state. .splice directly mutates the state. Learn to use .map, .filter & .reduce array functions
I have a react component that represents a document with text and some footnotes. The text should be rendered like this:
This the first footnote[1], this is the second[2].
Here is another [3].
As I'm rendering my component, I want to count up every time I see a footnote so that it's incremented. The tree can be many levels deep so you can't assume that all the footnotes are direct children of the main component.
This should also be dynamic, so that adding references updates the count.
I can't think of a very 'Reacty' way of doing this. Context (as frowned upon as it is) does not seem like the right thing, and otherwise, you have no information about neighboring components.
I think I would handle it like this...
In your container or top-level component, create an array for holding footnotes. Then pass this array down as a prop to any component that may render footnotes, and also to a footnote-rendering component which must be rendered after any of the other components.
const DocComponent = () => {
const footnotes = [];
return (
<div>
<SomeContent footnotes={footnotes} />
<SomeOtherContent footnotes={footnotes} />
<EvenDifferentContent footnotes={footnotes} />
<Footnotes footnotes={footnotes} />
</div>
);
};
Note that the footnotes array must be passed down the hierarchy via props to all components that could render a reference to a footnote. Every time a component renders a footnote reference, it adds a footnote to the array like so:
const SomeContent = ({footnotes}) => {
footnotes.push('This is the footnote text.');
const footnoteIndex = footnotes.length;
return (<p>Hermansen and Shihipar, et al [{footnoteIndex}]</p>);
};
When execution arrives to the Footnotes component, the same footnotes array instance will be passed via prop to it. At that point in execution, the array will be populated with all the footnotes that need to be displayed. And you can just render them in a straightforward way:
const Footnotes = ({footnotes}) => {
const inner = footnotes.map(
(footnote, index) => (<li>[{index+1}] {footnote}</li>) );
return (<ul>{inner}</ul>);
};
This implementation is definitely coupled to the rendering order of components. So the component order in your rendering should match the visual order you would want footnotes to appear in.
Here is a jsfiddle - https://jsfiddle.net/69z2wepo/79222/