I am using reselect in my ReactJs code. Here is the code snippet. Due to large file, i am ommitting out unnecessary code from it.
import { createSelector } from 'reselect';
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
const searchViewSelector = state => state.todo.userView
export const filteredTodos = createSelector(
[todoSelector, searchTermSelector, searchViewSelector],
(todos, searchTerm, searchView) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
As you can notice the paramters for createSelector. As we know createSelector expects 2 arguments: an array of input selector(s) as the 1st argument and a function as the 2nd argument
In this case, array of input selectors is 3. ([todoSelector, searchTermSelector, searchViewSelector])
But in my actual code, array of input selectors are 9. I need to reduce the count from 9 to less than 4 due to sonar issues.
How can i minimize the array of input selectors and still make it work as expected. I search a lot online but i didnt find any ifno related to it. Please any suggestions ?
If you want to reduce the number of arguments per selector functions, you can separate logic into multiple pieces and use createSelector function result as an argument for another createSelector function. Something like this:
const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;
const searchViewSelector = state => state.todo.userView
export const filteredTodosByTerm = createSelector(
[todoSelector, searchTermSelector],
(todos, searchTerm) => {
return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
}
);
export const filteredTodosByView = createSelector(
[filteredTodosByTerm, searchViewSelector],
(todos, searchView) => {
return todos.filter(todo => todo.title.match(new RegExp(searchView, 'i')));
}
);
)
Related
I've created two components which together create a 'progressive' style input form. The reason I've chosen this method is because the questions could change text or change order and so are being pulled into the component from an array stored in a JS file called CustomerFeedback.
So far I've been trying to add a data handler function which will be triggered when the user clicks on the 'Proceed' button. The function should collect all of the answers from all of the rendered questions and store them in an array called RawInputData. I've managed to get this to work in a hard coded version of SurveyForm using the code shown below but I've not found a way to make it dynamic enough to use alongside a SurveyQuestion component. Can anybody help me make the dataHander function collect data dynamically?
There what I have done:
https://codesandbox.io/s/angry-dew-37szi2?file=/src/InputForm.js:262-271
So, we can make it easier, you just can pass necessary data when call handler from props:
const inputRef = React.useRef();
const handleNext = () => {
props.clickHandler(props.reference, inputRef.current.value);
};
And merge it at InputForm component:
const [inputData, setInputData] = useState({});
const handler = (thisIndex) => (key, value) => {
if (thisIndex === currentIndex) {
setCurrentIndex(currentIndex + 1);
setInputData((prev) => ({
...prev,
[key]: value
}));
}
};
// ...
<Question
// ...
clickHandler={handler(question.index)}
/>
So, you wanted array (object more coninient I think), you can just save data like array if you want:
setInputData(prev => [...prev, value])
Initially, I thought you want to collect data on button clicks in the InputForm, but apparently you can do without this, this solution is simpler
UPD
Apouach which use useImperativeHandle:
If we want to trigger some logic from our child components we should create handle for this with help of forwarfRef+useImperativeHandle:
const Question = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
React.useImperativeHandle(
ref,
{
getData: () => ({
key: props.reference,
value: inputRef.current.value
})
},
[]
);
After this we can save all of our ref in parent component:
const questionRefs = React.useRef(
Array.from({ length: QuestionsText.length })
);
// ...
<Question
key={question.id}
ref={(ref) => (questionRefs.current[i] = ref)}
And we can process this data when we want:
const handleComplete = () => {
setInputData(
questionRefs.current.reduce((acc, ref) => {
const { key, value } = ref.getData();
return {
...acc,
[key]: value
};
}, {})
);
};
See how ref uses here:
https://reactjs.org/docs/forwarding-refs.html
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
I still strongly recommend use react-hook-form with nested forms for handle it
How to prevent component re-rendering, when i have an hierarchical array of objects (array of objects) using redux dispatching and memo
for example i have like this list of list of components
like
Elements =[ // an array in store.state variable
{name='a',
subEelemets:[
{name:'a-a',
components:[
{
name:'a-a-1',
component:'TextFiled',
value:'default value...'
},
]
},
]
},
]
i used redux to update components values inside my Elements array using dispatch but the performance downgrades because of many re-rendering,
i tried with memo to prevent re rendering but it also updates the entire Elements array because i used useSelector,
const ImportedComponent = memo((props) => {
const LoadableComponent = loadable(() =>
import("../lib" + props.compName))
);
return (
<LoadableComponent
{...props}
id={props.id}
/>)},(a,b)=>a.id===b.id?true:false)
here the code how i redner my compoents
const selectTodoIds = state => state.components.map((todo,index) => todo)
const updateComp = () => {
const Components = useSelector(selectCompsIds, shallowEqual)
const renderedListItems = Components.map((CompId) =>
<ImportedComponent key={CompId} elementID={CompId} />
)
return <ul className="todo-list">{renderedListItems}</ul>
}
it works when i used a flat array as presented in the tutorial https://redux.js.org/tutorials/fundamentals/part-5-ui-react but not for hierarchical one, because i should use the "name" to define which subelement i'm updating,
is there any strategy to solve this problem?
ps: the code is just for clarification,
Rather than memo you could try useMemo:
const ImportedComponent = (props) => {
const LoadableComponent = useMemo(() => {
return loadable(() => import("../lib" + props.compName));
},[props.compName]);
...
memo only helps when there's lots of re-renders with the same props, which it doesn't appear you have.
That's a complete shot in the dark, though. Can you post a link to a stackblitz, maybe? It'd be nice to see more of your code in context.
I'm using Redux-toolkit with entityAdapter for state mangement in my React App. I want to apply table sorting for each column where my table rows are entities of Redux-toolkit entityAdapter. I want to change the sortComparer fuction like this in my reducer.
sortEntities: (state,action) =>{
return {
...state,
sortComparer:(a:myEntityType,b:myEntityType)=> a.title.localCompare(b.title)
};
},
I dispatch sortEntities action on onClick handler of columns. This sort of changing the sortComparer is not throwing any rules voilation error but not working. Can somebody help me?
What you are doing in the above code is storing a function to the sortComparer property of your state. You aren't actually applying the sort function anywhere, and it's not advisable to store non-serializable data like functions in Redux.
The adapter that you create by calling createEntityAdapter is an object which helps you interact with the state. The state itself is just an array of ids and a dictionary object of entities. The sortComparer property of the adapter is not part of the state, so it cannot be modified by modifying the state.
There are a lot of ways to approach this.
For example, you could select all entities of Redux and sort them locally.
const useSortedEntities = (sortBy) => {
// get an array of entities with the selector from the entity adapter
const allEntities = useSelector(selectEntities);
// sort based on the current property
// use ... to avoid mutation
// would probably want to memoize this
return [...allEntities].sort(
(a, b) => a[sortBy].localCompare(b[sortBy])
);
}
const SomeComponent = () => {
const [sortProperty, setSortProperty] = useState('author');
const sortedList = useSortedEntities(sortProperty);
...
Or you could dispatch an action with the sort property in the payload and save the sort property in Redux. You can then use createSelector to create a selector for the sorted data.
const mySlice = createSlice({
name: 'someName',
initialState: myAdapter.getInitialState({
// additional properties to include in initial state
sortBy: 'author'
}),
reducers: {
sortEntities: (state, action: PayloadAction<string>) => {
state.sortBy = action.payload;
}
...
const selectSortedEntities = createSelector(
// first select entities with the selector from the adapter
selectEntities,
// next select the sort order
(state: RootState) => state.pathToSlice.sortBy
// then combine them to return a sorted array
(allEntities, sortBy) => [...allEntities].sort(
(a, b) => a[sortBy].localCompare(b[sortBy])
);
)
const SomeComponent = () => {
const sortedList = useSelector(selectSortedEntities);
const dispatch = useDispatch();
const onClick = () => {
dispatch(sortEntities('title'));
}
...
i use react.js to build my spa app.
I use functional style to make my components.
As the business logic gonna bigger, there are inevitably many functions.
So i tried to divide in to multiple components. Because it's hard to put many codes in one file even if it is just a 1 component.
However, this also has obvious limitations. In the case of complex components, there are a large number of event callback functions in addition to the functions directly called by the user.
Depending on the size of the component, it is sometimes difficult to write all the logic in one jsx file, so I want to divide the code into different files as needed. (Like c# partial class)
However, this is not easy. As an example, let's assume that the callback functions are made external functions of other files and imported into this component jsx file and used. But it seems that the component states, props information and the dispatch function also should be passed as parameters to the function. This seems hassle but except this, i have no idea a way to access this component's states, props, dispatch function from a function in another file.)
//For example of callback function
const onHoldButtonClicked = (state, props, dispatch, event) =>
{
~~~
}
//For example of normal function
const updateValidUser = (state, props, dispatch, userInfo, data) =>
{
let id = userInfo.id;
if(id == data.passID)
{
if(props.valid == 10)
dispatch({action: 'ChangeUser', user: id});
}
}
In React, how to divide logic(functions) when the logic gonna bigger in one component? (In general case)
Even if it is divided into several components, a big component inevitably has many functions.
I would recommend to extract logic into hooks and place these hooks into their own files.
hook.js
const useAwesomeHook = () => {
const [someState, setSomeState] = useState("default");
const myCoolFunction = useCallback(() => {
console.log('do smth cool', someState);
}, [someState]);
return myCoolFunction;
};
export default useAwesomeHook;
main.js
import useAwesomeHook from './hook';
const Main = ({ someProperty }) => {
const myCoolFunction = useAwesomeHook(someProperty);
return <button onClick={myCoolFunction}>Click me</button>;
};
Here is an example for logic and business and component separation.
The separation makes your code testable, atomic, maintainable, readable and SRP(single responsibility rule )
// Presentational component
const QuantitySelector = () => {
const { onClickPlus, onClickMinus, state } = useQuantitySelector();
const { message, value } = state;
return (
<div className="quantity-selector">
<button onClick={onClickMinus} className="button">
-
</button>
<div className="number">{value}</div>
<button onClick={onClickPlus} className="button">
+
</button>
<div className="message">{message}</div>
</div>
);
};
export default QuantitySelector;
and below code is the above component logic
import { useState } from "react";
// Business logic. Pure, testable, atomic functions
const increase = (prevValue, max) => {
return {
value: prevValue < max ? prevValue + 1 : prevValue,
message: prevValue < max ? "" : "Max!"
};
};
const decrease = (prevValue, min) => {
return {
value: prevValue > min ? prevValue - 1 : prevValue,
message: prevValue > min ? "" : "Min!"
};
};
// Implementation/framework logic. Encapsulating state and effects here
const useQuantitySelector = () => {
const [state, setState] = useState({
value: 0,
message: ""
});
const onClickPlus = () => {
setState(increase(state.value, 10));
};
const onClickMinus = () => {
setState(decrease(state.value, 0));
};
return { onClickPlus, onClickMinus, state };
};
export default useQuantitySelector;
I separate components into the logic and UI by function composition.The idea came from recompse which I used before hooks came into react.
you can add two helper functions.
first one is compose you can learn more about it here:
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
export default compose;
the second one is withHooks:
import React from 'react';
export default (hooks) =>
(WrappedComponent) =>
(props) => {
const hookProps = hooks(props);
return (
<WrappedComponent
{...props}
{...hookProps}
/>
);
}
with these two functions, you can put your logic in the hooks file and pass the as props to your UI with compose file you can see sandbox example here
what I usually do is create a folder for the big component, in the folder I create a functions file and put functions with state passed and other params necessary . as simple as that .
export const increment=(count,setCount)=>{...}
.
.
and in your component
import{increment,....} from './functions'
const Component=(props)=>{
const [count,setCount]=useState(1)
return <div>
<button onClick={e=>increment(count,setCount)}> count ={count}</button>
</div>
}
I'm using reselect to query my state and props.
When I add a log statement to my component, I can see that it is being rendered over and over again.
If I return a find (non-array, one object) from the getFiltered method I see that it doesn't render over and over again. If I return a filter from the getFiltered method I see that it does render over and over again.
I assume its to do with some array === array going on somewhere. How do I fix it? I've tried using lodash's isEqual as per the documentation. I'm feeding the result of getFiltered into my state. It looks like the state thinks its changing for some reason
export const getProjects = state => state.availability.projects.list;
export const getProjectId = (state, props) => props.project.id;
// WORKS! - Don't see re-render log statements
export const getFiltered = createSelector([getProjects, getProjectId],
(projects, projectId) => projects.find(project => project));
// Doesn't work - I do see re-render statements in the console
export const getFiltered = createSelector([getProjects, getProjectId],
(projects, projectId) => projects.filter(project => project));