Related
I have an array of objects. So for every object, which has subItems, I'm trying to add a button to it. On click of the particular button, the name of the particular button should toggle between 'Expand' and 'Hide'. I'm displaying them using map.
export default function App() {
const [button,setButton] = useState([
{pin: 'Expand', id: 0},
{pin: 'Expand', id: 1},
{pin: 'Expand', id: 2},
]);
const dataObj = [
{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking','sleeping']
},
{
title: 'Contact',
subItems: ['phone','mobile']
}
];
const expandFunc = (ind) => {
// toggle logic here
}
return (
<div className="App">
{
dataObj.map((arr,ind) => (
<div>
<span>{arr.title}:</span>
{
// console.log(ind)
arr.subItems &&
<button onClick={() => expandFunc(ind)}>{button[ind].pin}</button>
}
</div>
))
}
</div>
);
}
This is how the output looks -
If I click on service button, then the button name should toggle between 'expand' and 'hide'. Could someone help me with this?
You need to update your state by determining new pin based on current state, try using Array.map:
const expandFunc = (ind) => {
const togglePin = oldPin => oldPin === "Expand" ? "Hide" : "Expand";
const updatedButtons = button.map((btn, index) =>
ind === index ? { ...btn, pin: togglePin(btn.pin) } : btn);
setButton(updatedButtons);
}
You can use something like this, I'll also suggest combining dataObj into button state, and using key while mapping elements in React helps to skip them in the rendering process making your site faster.
export default function App() {
const [button, setButton] = useState([{
expanded: false,
id: 0
},
{
expanded: false,
id: 1
},
{
expanded: false,
id: 2
},
]);
const dataObj = [{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking', 'sleeping']
},
{
title: 'Contact',
subItems: ['phone', 'mobile']
}
];
const toggleExpandFunc = useCallback((ind) => {
// toggle logic here
setButton([...button.map(btn => btn.id === ind ? { ...btn,
expanded: !btn.expanded
} : btn)]);
}, [button]);
return ( <
div className = "App" > {
dataObj.map((arr, ind) => ( <
div >
<
span > {
arr.title
}: < /span> {
// console.log(ind)
arr.subItems &&
<
button onClick = {
() => toggleExpandFunc(ind)
} > {
button[ind].expanded ? 'Expanded' : 'Hide'
} < /button>
} <
/div>
))
} <
/div>
);
}
You can also need another state like Toggle and expnadFunc can be handled like this;
const expandFunc = (ind) => {
let tmp = [...button];
tmp.map((arr, index) => {
if (index === ind) {
return (arr.pin = arr.pin === 'Toggle' ? 'Expand' : 'Toggle');
}
return arr;
});
setButton(tmp);
};
First time poster so let me know if more information is need.
Trying to figure out why my global state using context API is being updated even when my setSate method is commented out. I thought i might have been mutating the state directly accidently but I dont believe I am
"specialModes" in actionOnClick() is the state in question
const SpecialFunctions: FC = (props: Props) => {
const { currentModeContext, specialModesContext: specialActionsContext, performCalc, inputValueContext } = useContext(AppContext)
const { specialModes, setSpecialModes } = specialActionsContext
const { currentMode, setCurrentMode } = currentModeContext
const { inputValue, setInputValue } = inputValueContext
const categoryOnClick = (index: number) => {
setCurrentMode(specialModes[index])
console.log(specialModes[index].title);
}
const actionOnClick = (action: IAction) => {
let newAction = action
newAction.value = performCalc()
let newSpecialModes = specialModes.map((mode) => {
if (mode === currentMode) {
let newMode = mode
newMode.actions = mode.actions.map((element) => {
if (element === action) {
return newAction
}
else return element
})
return newMode
}
else return mode
})
//setSpecialModes(newSpecialModes)
}
let headings = specialModes.map((categorgy, index) => {
return <Heading isActive={categorgy === currentMode ? true : false} onClick={() => categoryOnClick(index)} key={index}>{categorgy.title}</Heading>
})
let actions = currentMode.actions.map((action, index) => {
return (
<Action key={index} onClick={() => actionOnClick(action)}>
<ActionTitle>{action.title}</ActionTitle>
<ActionValue>{action.value}</ActionValue>
</Action>
)
})
return (
<Wrapper>
<Category>
{headings}
</Category>
<ActionsWrapper toggleRadiusCorner={currentMode === specialModes[0] ? false : true}>
{actions}
</ActionsWrapper>
</Wrapper>
)
}
Context.tsx
interface ContextType {
specialModesContext: {
specialModes: Array<ISpecialModes>,
setSpecialModes: React.Dispatch<React.SetStateAction<ISpecialModes[]>>
},
currentModeContext: {
currentMode: ISpecialModes,
setCurrentMode: React.Dispatch<React.SetStateAction<ISpecialModes>>
},
inputValueContext: {
inputValue: string,
setInputValue: React.Dispatch<React.SetStateAction<string>>
},
inputSuperscriptValueContext: {
inputSuperscriptValue: string,
setInputSuperscriptValue: React.Dispatch<React.SetStateAction<string>>
},
performCalc: () => string
}
export const AppContext = createContext({} as ContextType);
export const ContextProvider: FC = ({ children }) => {
const [SpecialModes, setSpecialModes] = useState([
{
title: 'Rafter',
actions: [
{
title: 'Span',
active: false
},
{
title: 'Ridge Thickness',
active: false
},
{
title: 'Pitch',
active: false
}
],
},
{
title: 'General',
actions: [
{
title: 'General1',
active: false
},
{
title: 'General2',
active: false
},
{
title: 'General3',
active: false
}
],
},
{
title: 'Stairs',
actions: [
{
title: 'Stairs1',
active: false
},
{
title: 'Stairs2',
active: false
},
{
title: 'Stairs3',
active: false
}
],
}
] as Array<ISpecialModes>)
const [currentMode, setCurrentMode] = useState(SpecialModes[0])
const [inputValue, setInputValue] = useState('0')
const [inputSuperscriptValue, setInputSuperscriptValue] = useState('')
const replaceCharsWithOperators = (string: string): string => {
let newString = string.replaceAll(/\s/g, '') // delete white space
newString = newString.replace('×', '*')
newString = newString.replace('÷', '/')
console.log(string)
console.log(newString)
return newString
}
const performCalc = (): string => {
let originalEquation = `${inputSuperscriptValue} ${inputValue} =`
let equation = inputSuperscriptValue + inputValue
let result = ''
equation = replaceCharsWithOperators(equation)
result = eval(equation).toString()
setInputSuperscriptValue(`${originalEquation} ${result}`)
setInputValue(result)
console.log(result)
return result
}
return (
<AppContext.Provider value={
{
specialModesContext: {
specialModes: SpecialModes,
setSpecialModes: setSpecialModes
},
currentModeContext: {
currentMode,
setCurrentMode
},
inputValueContext: {
inputValue,
setInputValue
},
inputSuperscriptValueContext: {
inputSuperscriptValue,
setInputSuperscriptValue
},
performCalc
}}>
{children}
</AppContext.Provider>
)
}
In your mode.actions.map() function you are indirectly changing actions field of your original specialModes array.
To fix this problem you need to create shallow copy of specialModes array Using the ... ES6 spread operator.
const clonedSpecialModes = [...specialModes];
let newSpecialModes = clonedSpecialModes.map((mode) => {
// rest of your logic here
})
I have a state object that contains an array of objects:
this.state = {
feeling: [
{ name: 'alert', status: false },
{ name: 'calm', status: false },
{ name: 'creative', status: false },
{ name: 'productive', status: false },
{ name: 'relaxed', status: false },
{ name: 'sleepy', status: false },
{ name: 'uplifted', status: false }
]
}
I want to toggle the boolean status from true to false on click event. I built this function as a click handler but it doesn't connect the event into the state change:
buttonToggle = (event) => {
event.persist();
const value = !event.target.value
this.setState( prevState => ({
status: !prevState.status
}))
}
I'm having a hard time following the control flow of the nested React state change, and how the active event makes the jump from the handler to the state object and vice versa.
The whole component:
export default class StatePractice extends React.Component {
constructor() {
super();
this.state = {
feeling: [
{ name: 'alert', status: false },
{ name: 'calm', status: false },
{ name: 'creative', status: false },
{ name: 'productive', status: false },
{ name: 'relaxed', status: false },
{ name: 'sleepy', status: false },
{ name: 'uplifted', status: false }
]
}
}
buttonToggle = (event) => {
event.persist();
const value = !event.target.value
this.setState( prevState => ({
status: !prevState.status
}))
}
render() {
return (
<div>
{ this.state.feeling.map(
(stateObj, index) => {
return <button
key={ index }
onClick={ this.buttonToggle }
value={ stateObj.status } >
{ stateObj.status.toString() }
</button>
}
)
}
</div>
)
}
}
In order to solve your problem, you should first send the index of the element that is going to be modified to your toggle function :
onClick = {this.buttonToggle(index)}
Then tweak the function to receive both the index and the event.
Now, to modify your state array, copy it, change the value you are looking for, and put it back in your state :
buttonToggle = index => event => {
event.persist();
const feeling = [...this.state.feeling]; //Copy your array
feeling[index] = !feeling[index];
this.setState({ feeling });
}
You can also use slice to copy your array, or even directly send a mapped array where only one value is changed.
Updating a nested object in a react state object is tricky. You have to get the entire object from the state in a temporary variable, update the value within that variable and then replace the state with the updated variable.
To do that, your buttonToggle function needs to know which button was pressed.
return <button
key={ index }
onClick={ (event) => this.buttonToggle(event, stateObj.name) }
value={ stateObj.status } >
{ stateObj.status.toString() }
</button>
And your buttonToggle function could look like this
buttonToggle = (event, name) => {
event.persist();
let { feeling } = this.state;
let newFeeling = [];
for (let index in feeling) {
let feel = feeling[index];
if (feel.name == name) {
feel = {name: feel.name, status: !feel.status};
}
newFeeling.push(feel);
}
this.setState({
feeling: newFeeling,
});
}
Here's a working JSFiddle.
Alternatively, if you don't need to store any more data per feeling than "name" and "status", you could rewrite your component state like this:
feeling: {
alert: false,
calm: false,
creative: false,
etc...
}
And buttonToggle:
buttonToggle = (event, name) => {
event.persist();
let { feeling } = this.state;
feeling[name] = !feeling[name];
this.setState({
feeling
});
}
I think you need to update the whole array when get the event. And it is better to not mutate the existing state. I would recommend the following code
export default class StatePractice extends React.Component {
constructor() {
super();
this.state = {
feeling: [
{ name: "alert", status: false },
{ name: "calm", status: false },
{ name: "creative", status: false },
{ name: "productive", status: false },
{ name: "relaxed", status: false },
{ name: "sleepy", status: false },
{ name: "uplifted", status: false },
],
};
}
buttonToggle = (index, value) => (event) => {
event.persist();
const toUpdate = { ...this.state.feeling[index], status: !value };
const feeling = [...this.state.feeling];
feeling.splice(index, 1, toUpdate);
this.setState({
feeling,
});
};
render() {
return (
<div>
{this.state.feeling.map((stateObj, index) => {
return (
<button
key={index}
onClick={this.buttonToggle(index, stateObj.status)}
value={stateObj.status}
>
{stateObj.status.toString()}
</button>
);
})}
</div>
);
}
}
I try to implement global search with new material ui next table component, I have handleSearch method, wich recives event, and than I use regexp to check if event.target.value the same as in table. But when I delete string in search, the columns not update. It starts to search only I start type. How to search by number not only by string in this case
const columns = [
{
dataKey: 'deviceType',
label:'Device Type',
numeric: false,
}, {
dataKey: 'deviceID',
label:'Device ID',
sortable: true,
numeric: true,
// cellRenderer: ({item, key}) =>
// <Button >Default</Button>,
}, {
........
}]
const data = [
{ key: 1, deviceType: 'Tag', deviceID: 1, name:'Tag For sending an ', location: 'Room_104', status: 'assigned'},
{ key: 2, deviceType: 'Tag', deviceID: 2, name:'Tag For sending an ', location: 'Room_104', status: 'assigned'},
{.......},
]
class EnhancedTable extends Component {
state = {
selected: [],
data,
order: {
direction: 'asc',
by: 'deviceID',
},
search: '',
}
handleSearch = event => {
debugger
const {data} = this.state
let filteredDatas = []
filteredDatas = data.filter(e => {
let mathedItems = Object.values(e)
let returnedItems
mathedItems.forEach(e => {
const regex = new RegExp(event.target.value, 'gi')
if (typeof e == 'string')
returnedItems = e.match(regex)
})
return returnedItems
})
this.setState({data: filteredDatas, search: event.target.value})
}
render = () => {
const {data, search} = this.state
return (
<Paper>
<Table
data={data}
search={search}
onSearch={this.handleSearch}
/>
</Paper>)
}
}
export default EnhancedTable
fuzzyContains = (text, search) => {
debugger
if (!text)
return false
if (!search)
return true
search = search.toLowerCase()
text = text.toString().toLowerCase()
let previousLetterPosition = -1
return search.split('').every(s => {
debugger
previousLetterPosition = text.indexOf(s, previousLetterPosition + 1)
return previousLetterPosition !== -1
})
}
handleSearch = search => {
const {data} = this.state
// debugger
let filteredData = data.filter(x => Object.keys(x).some(key =>
// debugger
this.fuzzyContains(x[key], search)
))
console.log(filteredData)
this.setState({filteredData, search})
}
Main app:
class App extends Component {
constructor(props) {
super(props);
this.state = {
totalCheckBoxes: 0,
checkboxesClicked: 0,
percentage: 0,
};
this.checkboxClick = this.checkboxClick.bind(this);
}
checkboxClick(type) {
(type) ? this.setState({ checkboxesClicked: checkboxesClicked++ }) :
(type > 0) ? this.setState({ checkboxesClicked: checkboxesClicked-- }) : this.setState({ checkboxesClicked: 0 });
}
render() {
// grab steps
const { steps } = this.props;
const { totalCheckBoxes } = this.state;
const { checkboxClick } = this;
// add rendered steps to .allTheSteps
return (
ce('div', { className:'allTheSteps' },
ce(pagination, { steps }),
Object.values(steps).map((step, i) =>
ce(Steps, { step, key: v4(), i, checkboxClick }),
)
)
);
};
};
Child component:
const Steps = ({ step, i, checkboxClick }) =>
ce( "div", {
className: `mainBoxes clearfix playbook_step_${i}`,
key: v4(),
},
ce('span', {
className: 'steps'
}, step.id + 1 + ' - '),
ce("strong", {
className: "titleText",
key: v4(),
}, step.name),
( step.summary.length ) ? ce('div', { className: 'step__summary' }, step.summary) : null,
ce( "div", {
className: "stepArticle__content",
key: v4(),
},
step.articles.map(({ name, url }, j) => ce(Articles, { name, url, key: v4(), j, checkboxClick }))
)
);
Grandchild component:
class Articles extends Component {
constructor(props) {
super(props);
this.state = {
notes: false,
questions: false,
};
this.addNotes = this.addNotes.bind(this);
this.askQuestions = this.askQuestions.bind(this);
}
addNotes() {
this.setState({ notes: !this.state.notes });
}
askQuestions() {
this.setState({ questions: !this.state.questions });
}
render(){
const { name, url, checkboxClick } = this.props;
const { notes, questions } = this.state;
const { addNotes, askQuestions } = this;
return ce('div', null, Article( { name, url, notes, questions, addNotes, askQuestions, checkboxClick } ));
}
}
const Article = ({ name, url, notes, questions, addNotes, askQuestions, checkboxClick }) => (
ce('div', { className: 'stepArticle step__'},
ce('div', {className: 'clearfix'},
ce('div', {className: 'articleTitle'},
ce('input', {
type: 'checkbox',
name: 'done',
onClick: checkboxClick.bind(null, this),
className: 'checkBoxes'}
),
ce('a', {
className: 'checkLink',
target: '_blank',
href: url
}, name),
),
ce('div', {className: 'articleActions'},
ce('input', {
type: 'button',
value: 'Make Notes',
className: 'addNotes',
onClick: addNotes,
}),
ce('input', {
type: 'button',
value: 'Ask Clausehound',
className: 'askQuestions',
onClick: askQuestions,
}),
)
),
(notes) ? ce('textarea', {
className: 'text_areas notes notes__',
placeholder: 'My Notes: '
}) : null,
(questions) ? ce('textarea', {
className: 'text_areas questions questions__',
placeholder: 'Questions for Clausehound Research Team: ',
}) : null,
)
);
The app is a step by step instruction/tutorial and when a user is done with a step, they tick the checkbox and a percentage for completion is displayed. I want to calculate the percentage of checkboxes that have been clicked.
Currently I am trying to do this in checkboxClick function in the parent component. Is this the correct approach? The type needs to be a boolean so that we know whether a checkbox was checked or unchecked.
Codesandbox link.