I'm trying to override the default theme in material ui v5, at first i used this :
styleOverrides:{
root: ({ theme }) => ({
margin : theme.spacing(2)
}),
},
But then when reading the docs i saw they used this :
const lightTheme = createTheme({
components: {
MuiPaper: {
styleOverrides:{
root: ({ theme : {spacing} }) => ({
margin : spacing(2)
}),
},
},
},
});
Here i think they destructured the the spacing function from the theme object, what i dont understand is the syntax, why not do this :
root: ({ {spacing} }) => ({
margin : spacing(2)
}),
Where {spacing} is like taking out the spacing() from the theme object, no ?
The ":" in { theme : {spacing} } is what confuses me, i'm not familiar with that syntax and i dont want to make assumptions on what it precisely does, i read these 2 resources
es6 in depth destructuring
Destructuring assignment - MDN
But i still couldn't find the anwer, if someone could explain it i'd be grateful.
{ {spacing} } won't do the trick. { theme: {spacing} } destructures two objects. First the object that is passed to the arrow function, then another object that is stored in the .theme property. In only variable that's being created here is spacing, which is .theme.spacing. Take this example:
const obj = {theme: {spacing: 1, color: 'blue'}};
const fn1 = ({theme: {spacing}}) => console.log(spacing);
fn1(obj);
The parameter likely looks like { theme: { spacing: ... } }, therefore to access it you need to define the full path (theme and spacing). If you only use ({ {spacing} }) it won't know that you want to access the spacing within the theme property. For instance in the following example ({ {spacing} }) would be ambiguous, potentially meaning two different functions:
const params = {
theme: {
spacing: (n) => `some css: ${n}`
},
otherProps: {
spacing: (n) => `other prop: ${n}`
},
}
const v1 = (params) => params.theme.spacing(2)
const v2 = ({ theme }) => theme.spacing(2)
const v3 = ({ theme: { spacing } }) => spacing(2)
console.log(v1(params))
console.log(v2(params))
console.log(v3(params))
Related
I am trying to learn Zustand and came across a tricky question.
Can I create a reusable function to set my state?
The example is very contrived, but I would like to display animal populations across many React components. The idea is to use the same increasePopulation function for any animal. I believe vanilla JavaScript uses the bracket notation for this, but I am struggling to make it work with Zustand
This is my store:
import create from 'zustand'
const useAnimalStore = create((set) => ({
bears: 0,
cats: 5,
dogs: 20,
increasePopulation: (animal) => set((state) => ({ [animal]: state.[animal] + 1 })),
}))
Many thanks and apologies if this is too newbie-like question
You were just using dot and bracket notation at the same time.
const useAnimalStore = create((set) => ({
bears: 0,
cats: 5,
dogs: 20,
increasePopulation: (animal) =>
// state[animal] not state.[animal]
set((state) => ({ [animal]: state[animal] + 1 }))
}));
CodeSandBox : https://codesandbox.io/s/epic-moore-mfr1f2?file=/src/store.js:31-200
instead of direct using variables wrap those into an object and update object with zustand set funtion and spread operator.
import create from 'zustand'
const useAnimalStore = create((set) => ({
animals: {
bears: 0,
cats: 5,
dogs: 20,
},
increasePopulation: (animal) => set((state) => ({ animals: { ...state.animals, [animal]: state.animals[animal] + 1 } })),
}))
Hello I am struggling to properly update my state from my child component to my parent.
Basically I am trying to set the current state to true onclick.
This is my parent component:
export default function Layout({ children }: Props) {
const [navigation, setNavigation] = useState([
{ name: 'Dashboard', href: '/', icon: HomeIcon, current: true },
{ name: 'Create Fact', href: '/facts/create', icon: UsersIcon, current: false },
{ name: 'Documents', href: '/documents', icon: InboxIcon, current: false }
])
return (
<>
<Sidebar navigation={navigation} setNavigation={setNavigation} />
This is my child Component (Sidebar)
type Props = {
navigation: Array<{
name: string
href: string
icon: any
current: boolean
}>
setNavigation: (
navigation: Array<{
name: string
href: string
icon: any
current: boolean
}>
) => void
}
const Sidebar = ({navigation, setNavigation}: Props) => {
const router = useRouter()
const toggleNavigation = (name: string) => {
// todo: Here I would like to properly update the state with the current selected navigation item (current)
const newNavigation = navigation.map(nav => {
if (nav.name === name) {
nav.current = true
return nav
}
})
}
return (
<nav className="flex-1 px-2 pb-4 space-y-1">
{navigation.map(item => (
<span
onClick={() => toggleNavigation(item.name)}
There are three problems:
You never call setNavigation with your new array.
You don't clear current on the formerly-current item.
Although you're creating a new array, you're reusing the objects within it, even when you change them, which is against the Do Not Modify State Directly rule.
To fix all three (see *** comments):
const toggleNavigation = (name: string) => {
const newNavigation = navigation.map(nav => {
if (nav.name === name) {
// *** #3 Create a *new* object with the updated state
nav = {...nav, current: true};
} else if (nav.current) { // *** #2 make the old current no longer current
nav = {...nav, current: false};
}
return nav;
});
// *** #1 Do the call to set the navigation
setNavigation(newNavigation);
};
Separately, though, I would suggest separating navigation out into two things:
The set of navigation objects.
The name of the current navigation item.
Then setting the navigation item is just setting a new string, not creating a whole new array with an updated object in it.
T.J. Crowder's solution and explanation are great.
Additionally, you can write that logic in a shorter syntax. Just a preference.
const newNavigation = navigation.map(nav => {
return nav.name === name
? { ...nav, current: true }
: { ...nav, current: false }
})
This question already has answers here:
Get a CSS value from external style sheet with Javascript/jQuery
(5 answers)
Closed 1 year ago.
I have a state variable initialized like this:
const App = () => {
const [parameters, setParameters] = useState({
"shape": "circle",
"fontFamily": "",
"size": 500
});
//...
// which is here passed to a component
return(
<MyCanvas props={parameters} />
);
}
The parameters array is used by another component that renders something on the canvas. I want that the parameters is on first load of the app, at runtime, to be what's currently defined in CSS, so I want to read that CSS property and init, in this case, the "fontFamily" field with what's written in CSS. Therefore want to read the #font-face {font-family: MyLocalFont;} property from the CSS definition.
So generally: How would I read a value during runtime from a CSS for initializing the useState variable?
A compile-time solution would not meet my needs. Changes to the CSS styles can be made without re-deploying my widget, therefore I need to read the actual CSS property values during runtime upon initialization.
import React, { useCallback } from 'react';
const defaultParameters = {
"shape": "circle",
"fontFamily": "",
"size": 500,
}
const App = () => {
const [parameters, setParameters] = useState(null);
const appRoot = useCallback(
(element) => {
const style = window.getComputedStyle(element);
const fontFamily = style.getPropertyValue('font-family');
setParameters({...defaultParameters, fontFamily });
}, []
);
//...
return (
<div ref={appRoot}>
{ parameters && <MyCanvas props={parameters} /> }
</div>
);
}
You can use the 'classNames' package.
https://www.npmjs.com/package/classnames
npm install --save classnames
import classNames from 'classnames';
const Testers = () => {
const [parameters, setParameters] = React.useState({
shape: 'circle',
fontFamily: '',
size: 500,
});
//I think you can change the font name when you need it.
React.useEffect(() => {
setParameters({ ...parameters, fontFamily: 'NotoSans' });
}, []);
return (
<div className={classNames(parameters></div>
....
Also, it would be better to designate a separate font as below method.
const [fonts, setFonts] = React.useState('fontName')
const [parameters, setParameters] = React.useState({
shape: 'circle',
size: 500,
});
React.useEffect(() => {
setFonts('NotoSans');
}, []);
<div className="header">
<div className={classNames(parameters, {
fontFamily : fonts
})}></div>
I have to find all the state value with key style and change the value with red in my nested state following below.
this.state = {
front: {
line1: {
style: "blue",
name: "name1"
},
line2: {
style: "blue",
name: "name2"
}
}
}
I have done a try like following, but it gives error.
Object.keys(this.state).forEach(function(k,prevState) {
this.setState(prevState => ({ [k]:
{...prevState[k], style: "red"}
}))
});
How can I update it?
You could use Object.keys on the front object to get an array with all the key names, and then use reduce on that and build up a new front object where you change all the style properties to "red".
class App extends React.Component {
state = {
front: {
line1: {
style: "blue",
name: "name1"
},
line2: {
style: "blue",
name: "name2"
}
}
};
onClick = () => {
this.setState(({ front }) => ({
front: Object.keys(front).reduce((acc, key) => {
acc[key] = {
...front[key],
style: "red"
};
return acc;
}, {})
}));
};
render() {
return (
<div>
<button onClick={this.onClick}>Change to red</button>
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You have proplem because you not use arrow function. You should use array function to have access to setState
Object.keys(this.state).forEach((k,prevState) => {
this.setState(prevState => ({ [k]:
{...prevState[k], style: "red"}
}))
});
Just make a copy of state and than loop through it and change the values of style key and update state
let copy = JSON.parse(JSON.stringify(this.state))
Object.keys(copy).forEach(e=>{
copy[key].style = 'red'
})
this.setState(copy)
I would suggest making a method to set red styles in the state.
You can copy and paste the code below and edit it to your liking.
setRedStyles = () => {
const newState = { ...this.state };
Object.keys(newState.front).forEach(prop => {
if (newState.front[prop].style) {
newState.front[prop].style = "red";
}
this.setState(newState);
});
}
You should be able to call setRedStyles() in your onClick function on call this function directly.
Your problem occurred because you used ES5 function in your forEach callback meaning that the callback has a function scope, where this refers to the callback function's context.
Solution 1: Use ES6 arrow function. Arrow functions have the scope of where it's defined.
Object.keys(this.state).forEach((k) => {
this.setState({ [k]: {...this.state[k], style: 'red' }})
});
Solution 2: Use the bind method.
The bind() method creates a new function that, when called, has its this keyword set to the provided value.
Object.keys(this.state).forEach(function(k) {
this.setState({ [k]: {...this.state[k], style: 'red' }})
}.bind(this));
I've got a component that gets two props, function and node(or string with label text), depends on these props I render the icon with some label. In future, I'm going to add more button and want to create the generic method that rendered this icon more flexible. So how to create such generic method for that?
const Wrapper = ({onRefresh, onExportToExcel, actionsLabel}) => {
return
{onRefresh && !!actionsLabel.refresh &&
<InlineIconButton name='refresh' label={actionsLabel.refresh} onClick={onRefresh} icon={<Autorenew/>} aria-label="Refresh"/>}
{onExportToExcel && !!actionsLabel.exportToExcel &&
<InlineIconButton name='exportToExcel' label={actionsLabel.exportToExcel} onClick={onExportToExcel} icon={<FileDownload/>} aria-label="ExportToExcel"/>}
}
<Wrapper onRefresh={()=> {}} onExportToExcel ={()=> {}} actionLabel={refresh: 'refresh', exportToExcel: 'export'}>
Maybe do something like:
const EXPORT_EXCEL = {
key: "EXPORT_EXCEL",
label: "export",
ariaLabel: "Export Excel",
icon: <Autorenew/>,
handler: params => { /* your function */ }
};
const REFRESH = {
key: "REFRESH",
label: "refresh",
ariaLabel: "Refresh",
icon: <FileDownload/>,
handler: params => { /* your function */ }
};
<Wrapper type={EXPORT_EXCEL} />;
const Wrapper = ({ type }) => {
return <InlineIconButton name={type.key} label={type.label} onClick={type.handler} icon={type.icon} aria-label={type.ariaLabel ? type.ariaLabel : type.label} />;
}
}
You even the possiblity to throw those EXPORT_EXCEL and REFRESH into array. Instead of having them loose put them in an array like so:
const BUTTONS = [
{
key: "EXPORT_EXCEL",
label: "export",
ariaLabel: "Export Excel",
icon: <Autorenew/>,
handler: params => { /* your function */ }
},
{
key: "REFRESH",
label: "refresh",
ariaLabel: "Refresh",
icon: <FileDownload/>,
handler: params => { /* your function */ }
},
];
And then loop through to create the Wrapper.
But then it's really up to you and your preferences and app's requirements
The entire idea behind React is to be able to create a unique component for every kind of usage. That is the entire philosophy behind React composability. Don't understand why would you want to wrap it.