I'm a bit stuck with redux. I want to create reducer that can update state onClick with data that provided in each button.
Here's my TabSlice.ts
interface ITabContext {
tabIndex: number,
posterUrlFetch: string,
tabData: {
fetchUrl: string;
title: string;
}[]
}
const initialState = {
tabIndex: 0,
posterUrlFetch: 'movie/popular',
tabData: [
{ fetchUrl: 'movie/popular', title: 'Trending' },
{ fetchUrl: 'movie/upcoming', title: 'Upcoming' },
{ fetchUrl: 'tv/popular', title: 'TV Series' },
]
}
const tabSlice = createSlice({
name: 'tab',
initialState: initialState as ITabContext,
reducers: {
changeTab(state, action: PayloadAction<ITab>) {
const newItem = action.payload;
return state = {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: [
{ fetchUrl: newItem.posterUrlFetch, title: newItem.posterUrlFetch },
]
}
}
}
})
Then I dispatch changeTab in my component and create function onClick:
const click = () => dispatch(changeTab({
tabIndex: 1,
posterUrlFetch: 'movie/popular',
tabData: [
{
fetchUrl: 'tv/latest',
title: 'TV Latest'
},
{
fetchUrl: 'movie/popular',
title: 'Popular'
},
{
fetchUrl: 'movie/latest',
title: 'Latest'
},
]
}));
As i click some info updates, but in tabData I have only first object. How to make it to push all data to tabData, not only first one? Thanks!
Remove return state = {} from your reducer function and instead return the object as a whole.
return {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: newItem.tabData,
};
For the payload's tabData you can pass newItem.tabData
Related
I have this object as a state in reactjs. I want to add another object inside the "childoptions: []" array which is nested inside the options array on button click.
How can I achieve this, pls help...
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
// {
// id: uuid(),
// optionName: 'red 🔴',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'green 🟢',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'blue 🔵',
// value: '',
// childOptions: [],
// },
],
},
parentId: null,
});
This is achievable by copy the prevState in the new state with the new object inserted inside the options array.
A more detailed explanation could be found at https://stackoverflow.com/a/26254086/9095807
setSelect1((prevState) => {
return {
...prevState,
properties: {
label: 'Select1',
options: prevState.properties.options.map(obj => obj.id === id ? { ...obj, childOptions: [...obj.childOptions, newChildOptions] } : obj),
}
}
})
const CategoriesData = [
{
name: "Category1",
isActive: true,
children: [
{
name: "Category1Child",
isActive: false,
}
]
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3",
isActive: true,
children: [
{
name: "Category3Child",
isActive: false,
}
]
}
];
const [disabledCategories, setDisabledCategories] = useState([]);
function notActiveCategories(categories) {
// Loop logs out at least 7 isActive: false categories.
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories([...disabledCategories, category]);
console.log(category);
}
});
};
useEffect(() => {
notActiveCategories(CategoriesData);
console.log(disabledCategories); // Only 1 category is in the array.
}, []);
I feel like the function the loop is in calling itself is causing the disabledCategories state to revert to when it was empty and that is leading to only the last step of the foreach to be set.
So how would i get this to loop through the categories array and have the disabledCategories state to contain all of the category objects that have isActive: false.
Which in the example of CategoriesData above, it would mean that the disabledCategories state would contain:
[
{
name: "Category1Child",
isActive: false,
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3Child",
isActive: false,
},
];
Try changing your setDisabledCategories to use the previous state param that comes from setState:
setDisabledCategories(prevState => [...prevState, category])
When multiple setState calls are batched together you need to be careful so they don't override each other. Using this method ensures that your setState calls are "chained" so you always get the updated state.
Way 1: Affect after recursive loop
function notActiveCategoriesRecusive(categories) {
let notActive = []
categories.forEach((category) => {
if (category.isActive) notActive = [...notActive, ...(notActiveCategories(category.children))];
if (!category.isActive) {
notActive.push(category)
}
});
return notActive
};
function notActiveCategories(categories) {
setDisabledCategories(notActiveCategoriesRecusive(categories)
}
Way 2: Get the last state because it doesn't has time to refresh
function notActiveCategories(categories) {
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories(oldState => ([...oldState, category]))
}
});
};
I'd only call setState once with the filtered array:
const findInactive = data =>
data.filter(e => !e.isActive)
.concat(...data.filter(e => e.children)
.map(e => findInactive(e.children)))
;
const categoriesData = [ { name: "Category1", isActive: true, children: [ { name: "Category1Child", isActive: false, } ] }, { name: "Category2", isActive: false, }, { name: "Category3", isActive: true, children: [ { name: "Category3Child", isActive: false, } ] } ];
const inactive = findInactive(categoriesData)
// the following is neeeded if it's possible for a
// node to have children and be inactive
// .map(({name, isActive}) => ({name, isActive}))
;
console.log(inactive);
//setDisabledCategories(inactive); // one time in React
This makes the code a lot easier to reason about and decouples React's API out from the filtering logic, which can be moved out to a generic function agnostic of React.
As others have mentioned, if you do want to call setState multiple times as a batch update, you can use the prevState callback to chain the updates: setDisabledCategories(prevState => [...prevState, category]);.
Assume that this state has initial data like this
const [options, setOptions] = useState({
ProcessType: [
{ value: 1, label: 'Type1' }, { value: 2, label: 'Type2' }
],
ResponsibleUser: [
{ value: 1, label: 'User1' }, { value: 2, label: 'User2' }
]
});
The following function will be called again and again when a post/put called
Help me to complete the commented area as described there.
const fetchProcesses = async () => {
await axios.get(`${process.env.REACT_APP_SERVER_BASE_URL}/processes/`)
.then((result) => {
/*
I want here to clear the existing data in options.ProcessType and
map result.data as { value: result.data.id , label: result.data.name },....
and push/concat it into to options.ProcessType but i want to keep the data
inside options.ResponsibleUser unchanged.
result.data is an array of objects like this,
[
{ id: 1 , name: 'Type1', desc : 'desc1', creator: 3, status: 'active' },
{ id: 2 , name: 'Type2', desc : 'desc2', creator: 6, status: 'closed' },
.....
.....
]
*/
})
}
Here is a solution
const fetchProcesses = async () => {
await axios.get(`${process.env.REACT_APP_SERVER_BASE_URL}/processes/`)
.then((result) => {
// solution
setOptions({ResponsibleUser: [...options.ResponsibleUser], ProcessType: result.data.map(row => ({value: row.id, label: row.name}))})
})
}
What the fetch returns is a list of items. I want to add those into state.
const [state, setState] = useState({
list: {
items: [],
}
});
fetch('http://example.com/list/')
// GET response: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }]
.then((resList) => resList.json())
.then((list) => {
list.forEach(({ name }) => {
const itemUrl = `https://example.com/list/${name}`;
fetch(itemUrl)
// GET responses:
// { name: 'foo', desc: '123' }
// { name: 'bar', desc: '456' }
// { name: 'baz', desc: '789' }
.then((itemRes) => itemRes.json())
.then((item) => {
setState((prevState) => ({
...prevState,
list: {
items: [...state.list.items, item]
},
});
})
})
}
})
console.log(state);
// result: [{ name: 'baz', desc: '789' }]
// but wanted: [{ name: 'foo', desc: '123' }, { name: 'bar', desc: '456' }, { name: 'baz', desc: '789' }]
In your case no need to use prevState in setState.
I prepared an example for you. Just be careful at using hooks.
https://codesandbox.io/s/recursing-wood-4npu1?file=/src/App.js:0-567
import React, { useState } from "react"
import "./styles.css"
export default function App() {
const [state, setState] = useState({
list: {
items: [
{ name: "foo", desc: "123" },
{ name: "bar", desc: "456" },
],
},
})
const handleClick = () => {
setState(() => ({
list: {
items: [...state.list.items, { name: "baz", desc: "789" }],
},
}))
}
return (
<div className="App">
<button onClick={handleClick}>Click Me </button>
<hr />
{JSON.stringify(state)}
</div>
)
}
You can't directly access the callback for useState hooks. This is how you can update state after fetching the data:
setState({
...state,
list: {
items:[...state.list.items, item]
},
});
class BottomPanelProgramTabs extends React.Component<Props, State> {
state = {
activeTab: this.props.children[0].props.label,
};
...
ESLint want me to use destructuring assignment on
this.props.children[0].props.label
any ideas on how to do that?
you can do like this.
For more reference about prefer-destructuring
class BottomPanelProgramTabs extends React.Component<Props, State> {
constructor(){
let [props] = this.props.children;
state = {
activeTab : props.label
}
}
class BottomPanelProgramTabs extends React.Component<Props, State> {
state = {
activeTab: 'default label'
};
componentDidMount = () => {
const [{ props: { label } }] = this.props.children;
this.setState({
activeTab: label || 'default label',
...
})
}
...
You can mix destructing by getting the first element with [] and get the props with {}.
For example:
using [child] will give us the first element in the array.
const children = [{
props: {
label: 'Some label'
}
},
{
props: {
label: 'Second label'
}
},
,
{
props: {
label: 'another label'
}
}]
const [child] = children;
console.log(child);
to get props we can continue mixing our destruction by adding [ {props} ] which returns props object.
const children = [{
props: {
label: 'Some label'
}
},
{
props: {
label: 'Second label'
}
},
,
{
props: {
label: 'another label'
}
}]
const [ {props} ] = children;
console.log(props);
to get the label from props will can do this [{ props: { label } }]
const children = [{
props: {
label: 'Some label'
}
},
{
props: {
label: 'Second label'
}
},
,
{
props: {
label: 'another label'
}
}]
const [{ props: { label } }] = children;
console.log(label);
With complex data
const children = [{
props: {
label: [{
data: {
title: 'complex destructor'
}
},
{
data: {
title: 'complex destructor 2'
}
}]
}
},
{
props: {
label: 'Second label'
}
},
,
{
props: {
label: 'another label'
}
}]
const [{ props: { label: [ { data: { title } } ] } }] = children;
console.log(title)