The problem is that calling unCheckAllColums updates the data of the set state (configEdit, configNoEdit) and additionally updates the value that comes from the props, and it should only update the configEdit
export fuction HookConfigsUser(configUser){
const initValues = {
configEdit: configUser; // this value only edit
configNoEdit: configUser; // this value use to recover info
}
const [values, setValues] = useState(initValues)
const unCheckAllColums = () => {
let editValue = { ...configEdit,}
let setListEdit = editValue.config.map((conf) => {
if (conf.enableEditShow === false) {
conf.show = true
} else {
conf.show = false
}
return conf
})
editValue.config = setListEdit
editValue.isEdit = true
const toSetAllstate = {
...values,
configEdit: editValue,
}
setValues(toSetAlstate)
}
const recoverInfo(){
setValues({
...values,
configEdit: values.configNoEdit,
}) }
return [unCheckAllColums, recoverInfo]
}
When calling the function all the variables are updated, those that are in setstate and the props and only the value inside state called configEdit should be updated.
Related
I am experienced js/React developer but came across case that I can't solve and I don't have idea how to fix it.
I have one context provider with many different state, but one state looks like following:
const defaultParams = {
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
}
const InnerPageContext = createContext()
export const InnerPageContextProvider = ({ children }) => {
const [params, setParams] = useState({ ...defaultParams })
const clearParams = () => {
setParams({...defaultParams})
}
console.log(defaultParams)
return (
<InnerPageContext.Provider
value={{
params: params,
setParam: setParam,
clearParams:clearParams
}}
>
{children}
</InnerPageContext.Provider>
)
}
I have one button on page, which calls clearParams function and it should reset params to default value.
But it does not works
Even when i console.log(defaultParams) on every provider rerendering, it seems that defaultParams variable is also changing when state changes
I don't think it's normal because I have used {...defaultParams} and it should create new variable and then pass it to useState hook.
I have tried:
const [params, setParams] = useState(Object.assign({}, defaultParams))
const clearParams = () => {
setParams(Object.assign({}, defaultParams))
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams(defaultParams)
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams({
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
})
}
None of above method works but 3-rd where I hard-coded same object as defaultParams.
The idea is to save dafult params somewhere and when user clears params restore to it.
Do you guys have some idea hot to make that?
Edit:
This is how I update my params:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type][key] = value
} else old[key] = value
console.log('Params', old)
return { ...old }
})
}
please show how you update the "params".
if there is something like this in the code "params.attrs.test = true" then defaultParams will be changed
if old[type] is not a simple type, it stores a reference to the same object in defaultParams. defaultParams.attrs === params.attrs. Since during initialization you destructuring an object but not its nested objects.
the problem is here: old[type][key] = value
solution:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type] = {
...old[type],
key: value,
}
} else old[key] = value
return { ...old }
})
}
Following is the data useEffect is returning in console log:
{
"sql": {
"external": false,
"sql": [
"SELECT\n date_trunc('day', (\"line_items\".created_at::timestamptz AT TIME ZONE 'UTC')) \"line_items__created_at_day\", count(\"line_items\".id) \"line_items__count\"\n FROM\n public.line_items AS \"line_items\"\n GROUP BY 1 ORDER BY 1 ASC LIMIT 10000",
[]
],
"timeDimensionAlias": "line_items__created_at_day",
"timeDimensionField": "LineItems.createdAt",
"order": {
"LineItems.createdAt": "asc"
}
I want to be able to render the above in my react app.
const ChartRenderer = ({ vizState }) => {
let ur = encodeURIComponent(JSON.stringify(vizState.query));
let u = "http://localhost:4000/cubejs-api/v1/sql?query=" + ur;
console.log(u)
useEffect(() => {
if(u !=="http://localhost:4000/cubejs-api/v1/sql?query=undefined") {
fetch(u)
.then(response => (response.json()))
.then(data => console.log(JSON.stringify(data, null, 4)))
}},[u]);
const { query, chartType, pivotConfig } = vizState;
const component = TypeToMemoChartComponent[chartType];
const renderProps = useCubeQuery(query);
return component && renderChart(component)({ ...renderProps, pivotConfig })
};
You would have to use a state variable to persist data and update it whenever the API returns some data. In a functional component, you can use the useState hook for this purpose.
const ChartRenderer = ({ vizState }) => {
// useState takes in an initial state value, you can keep it {} or null as per your use-case.
const [response, setResponse] = useState({});
let ur = encodeURIComponent(JSON.stringify(vizState.query));
let u = "http://localhost:4000/cubejs-api/v1/sql?query=" + ur;
console.log(u)
useEffect(() => {
if(u !=="http://localhost:4000/cubejs-api/v1/sql?query=undefined") {
fetch(u)
.then(response => (response.json()))
.then(data => {
// You can modify the data here before setting it to state.
setResponse(data);
})
}},[u]);
// Use 'response' here or pass it to renderChart
const { query, chartType, pivotConfig } = vizState;
const component = TypeToMemoChartComponent[chartType];
const renderProps = useCubeQuery(query);
return component && renderChart(component)({ ...renderProps, pivotConfig })
};
You can read more about useState in the official documentation here.
I'm using memoize-one on a React component that is basically a table with a rows that can be filtered.
Memoize works great for the filtering but when I want to insert a new row, it won't show up on the table until I either reload the page or use the filter.
If I check the state, the new row's data is in it, so presumably what is happening is that memoize is not allowing the component to re-render even if the state has changed.
Something interesting is that the Delete function works, I am able to delete a row by removing its data from the state and it will re-render to reflect the changes...
Here's the part of the code I consider relevant but if you would like to see more, let me know:
import React, { Component } from "react";
import memoize from "memoize-one";
import moment from "moment";
import {
Alert,
Card,
Accordion,
Button,
Table,
Spinner,
} from "react-bootstrap";
import PropTypes from "prop-types";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
import RoleMember from "./RoleMember";
import CreateMemberModal from "./CreateMemberModal";
class RoleContainer extends Component {
filter = memoize((roleMembers, searchValue, searchCriterion) => {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
});
constructor(props) {
super(props);
this.state = {
collapsed: true,
roleAttributes: [],
roleMembers: [],
isLoading: true,
};
}
componentDidMount = async () => {
const roleMembers = Object.values(await this.fetchRoleMembers());
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
return null;
});
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
this.setState({
roleMembers,
roleAttributes,
isLoading: false,
});
};
fetchRoleMembers = async () => {
const { roleAttributeName } = this.props;
return getRoleMembersDetailed(roleAttributeName);
};
createRoleMember = (newRoleMembers) => {
const { roleMembers } = this.state;
newRoleMembers.forEach((e) => {
roleMembers.push(e);
});
this.setState(
() => {
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
return { roleMembers };
},
() => {
console.log("sss", this.state);
}
);
};
deleteRoleMember = (alias) => {
this.setState((prevState) => {
const { roleMembers } = prevState;
return {
roleMembers: roleMembers.filter((member) => member.alias !== alias),
};
});
};
render() {
const {
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
} = this.props;
const { collapsed, isLoading, roleAttributes, roleMembers } =
this.state;
const filteredRoleMembers = this.filter(
roleMembers,
searchValue,
searchCriterion
);
return (
// continues...
I don't know if it's obvious but there are two functions called filter: this.filter that belongs to memoize and Array.prototype.filter().
I did look around and found these post that says Memoize can be overridden:
If you’ve ran into a UI bug, it is simple to just return false from myComparison to temporarily override the memoization, forcing a refresh on every re-render and returning to the default component behaviour.
But I'm not sure what they mean with "return false from component"
Here's a refactoring of your code to idiomatic React Hooks style (naturally dry-coded).
Note how filtering and sorting the role members is done using useMemo() in a way that doesn't modify state; that's because they can be always recomputed from the stateful data. So long as the useMemo()s' deps array is kept in sync (there're ESLint rules to help with this), this should work with no extra re-renders. :)
Similarly, if you use useCallback (which is a special case of useMemo), you need to keep their deps arrays in sync. If you don't use useCallback, those callbacks may cause re-renders since their identity changes per-render.
import React, { Component } from "react";
import moment from "moment";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
function filterRoleMembers(
roleMembers,
searchValue,
searchCriterion,
) {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
}
// TODO: maybe use lodash's `sortBy`?
function compareByAlias(a, b) {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
}
async function fetchRoleMembers(roleAttributeName) {
return getRoleMembersDetailed(roleAttributeName);
}
async function loadData(roleAttributeName) {
const roleMembers = Object.values(
await fetchRoleMembers(roleAttributeName),
);
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
return {
roleMembers,
roleAttributes,
};
}
const RoleContainer = ({
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
}) => {
const [collapsed, setCollapsed] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(true);
const [roleAttributes, setRoleAttributes] = React.useState([]);
const [roleMembers, setRoleMembers] = React.useState([]);
React.useEffect(() => {
loadData(roleAttributeName).then(
({ roleMembers, roleAttributes }) => {
setRoleAttributes(roleAttributes);
setRoleMembers(roleMembers);
setIsLoading(false);
},
);
}, [roleAttributeName]);
const createRoleMember = React.useCallback(
(newRoleMembers) => {
const updatedRoleMembers = roleMembers.concat(newRoleMembers);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const deleteRoleMember = React.useCallback(
(alias) => {
const updatedRoleMembers = roleMembers.filter(
(member) => member.alias !== alias,
);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const filteredRoleMembers = React.useMemo(
() =>
filterRoleMembers(roleMembers, searchValue, searchCriterion),
[roleMembers, searchValue, searchCriterion],
);
const sortedRoleMembers = React.useMemo(
() => [].concat(filteredRoleMembers).sort(compareByAlias),
[filteredRoleMembers],
);
return <>{JSON.stringify(sortedRoleMembers)}</>;
};
When I use useEffect I can prevent the state update of an unmounted component by nullifying a variable like this
useEffect(() => {
const alive = {state: true}
//...
if (!alive.state) return
//...
return () => (alive.state = false)
}
But how to do this when I'm on a function called in a button click (and outside useEffect)?
For example, this code doesn't work
export const MyComp = () => {
const alive = { state: true}
useEffect(() => {
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return
setSomeState('hey')
// warning, because alive.state is true here,
// ... not the same variable that the useEffect one
}
}
or this one
export const MyComp = () => {
const alive = {}
useEffect(() => {
alive.state = true
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return // alive.state is undefined so it returns
setSomeState('hey')
}
}
When a component re-renders, it will garbage collect the variables of the current context, unless they are state-full. If you want to persist a value across renders, but don't want to trigger a re-renders when you update it, use the useRef hook.
https://reactjs.org/docs/hooks-reference.html#useref
export const MyComp = () => {
const alive = useRef(false)
useEffect(() => {
alive.current = true
return () => (alive.current = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.current) return
setSomeState('hey')
}
}
Given a currentUser with an id, and a database of 'events' created by various users, I am trying to fetch a filtered list of events that include only events created by the current user, in other words, their events.
My plan of attack was to make a piece of the state a 'filter' which would be updated depending on various things, but right now I am having trouble even getting the most basic filter to work.
The error always occurs within the function immediately below this paragraph. I have gotten an error staying "currentUser" is undefined, or that "author_id" is undefined or "cannot read property author_id or undefined."
currentUser is defined on the store when I checked with getState.
filterMyEvents() {
let cid = currentUser.id
let e = this.props.events
let myEventsFilter = (events, id) => {
debugger
(cid === e[id].author_id)
}
myEventsFilter = myEventsFilter.bind(null, this.props.events)
this.props.updateFilter(myEventsFilter);
}
The filtering takes place in Selector.js
export const allEventsByFilter = (events, filter) => {
if (filter) {
return Object.keys(events).filter(filter);
} else {
return events;
}
}
User Container:
const mapStateToProps = (state) => ({
currentUser: state.session.currentUser,
requestEvents: requestEvents,
events: allEventsByFilter(state.events, state.filter)
});
const mapDispatchToProps = (dispatch) => ({
requestEvents: () => { dispatch(requestEvents()) },
updateFilter: (filter) => dispatch(updateFilter(filter))
})
Filter actions:
export const FilterConstants = {
UPDATE_FILTER: "UPDATE_FILTER"
};
export const updateFilter = (filter) => ({
type: FilterConstants.UPDATE_FILTER,
filter
});
Filters reducer
const _defaultFilters = () => {
return true;
}
const FiltersReducer = function(state = null, action){
if (action.type === FilterConstants.UPDATE_FILTER){
// const newFilter = {[action.filter]: action.value};
return action.filter;
} else {
return state;
}
};