Currently, I'm trying to use useState to change a value to an object.
However, the thing I've done doesn't make any errors.
So, I want to know if the way i use UseState is making issues later.
So, my code is like this :
this is where I make useState :
export const SettingContextProvider = props => {
const [title, setTitle] = useState({});
const [label, setLabel] = useState({});
}
and I pass to this component:
const DataSettingPage = () => {
const { layoutValue, chartValue, MainPage } = useContext(CommonStateContext);
const { SetTitle, SetLabel, SetDatas, SetLabels } = useContext(SettingContext);
const [changeLayout] = layoutValue;
const [chart] = chartValue;
const [title, setTitle] = SetTitle;
const [label, setLabel] = SetLabel;
{chart[index].key === "Bar" && (
<BarChart
title={title[id]}
label={label[id]}
/>
)}
and I'm using that state to here as a props :
import React from "react";
import { Bar } from "react-chartjs-2";
const BarChart = ({ title, label }) => {
let data = {
labels: [],
datasets: [
{
label: label,
data: [],
backgroundColor: ["#a7def8e1"],
},
],
};
return (
<>
<Bar
data={data}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: title,
color: "#345fbb",
font: { size: 22 },
},
},
}}
/>
</>
);
};
export default BarChart;
Thank you in advance
You don't need to destructure the passed value further in your child components.
these lines aren't needed:
const [title, setTitle] = SetTitle;
const [label, setLabel] = SetLabel;
Also, I didn't find SetDatas and SetLabels anywhere in your code, have you defined them?
Apart from these two issues, it is perfectly fine to define a state in a parent component and pass both the value and the function to the children:
Parent:
export const SettingContextProvider = props => {
const [title, setTitle] = useState({});
const [label, setLabel] = useState({});
return (<SettingContext.Provider value={[title,setTitle,label,setLabel]}>
<DataSettingPage/>
</SettingContext.Provider>
Child:
const DataSettingPage = () => {
const { layoutValue, chartValue, MainPage } = useContext(CommonStateContext);
const [title,setTitle,label,setLabel] =
useContext(SettingContext); // you can use them as needed
...
Are you calling React.createContext for your context objects? If not I believe your code may have issues as it's not the correct way to use useContext() according to React docs.
Your BarChart component looks like it's missing most of the data (only taking title and one label), so you should fill those in properly.
I would suggest to delete SettingContextProvider completely and simplify the DataSettingPage component as so:
const DataSettingPage = ({title, label}) => {
// I'm not sure what these are for so just leaving them here.
const { layoutValue, chartValue, MainPage } = useContext(CommonStateContext);
const [changeLayout] = layoutValue;
const [chart] = chartValue;
// DELETE
// const { SetTitle, SetLabel, SetDatas, SetLabels } = useContext(SettingContext);
const [title, setTitle] = useState(title)
const [label, setLabel] = useState(label)
{chart[index].key === "Bar" && (
<BarChart
title={title[id]} // <-- Where does your "id" come from??
label={label[id]}
/>
)}
Related
I do the weather app and need some help. In component Chart in options and series comes [object Object]. When you change something in the code, it is displayed. I think that the problem with useEffect? but I don't know how to fix that
import React, { useContext, useState, useEffect } from 'react';
import Chart from 'react-apexcharts';
import { Context } from '../../contex';
const WeatherGrapth = () => {
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, []);
return(
<div>
<Chart options={{
chart: {
id: 'weather-graph'
},
xaxis: {
categories: category,
title: {
text: 'Date',
},
},
yaxis: {
title: {
text: 'Temperature °C',
},
},
}}
series={[{
name: 'temp',
data: data
}]} type="line" height={'349px'} />
</div>
)
}
export default WeatherGrapth;
But as soon as I change something in the code, everything will update and a graph will appear.
As React doc says:
By default, effect runs both after the first render and after every update
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Probably At first dailyForecast context is empty or has not any valid data and after that it fills with data you should pass it to useEffect as dependency to run the effect at changes:
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, [dailyForecast]);
At the moment, there is a component that is essentially copied 4 times. I would like to make it more abstract, and simply render it 4 times, and pass in the dynamic data each time. The data that's passed into each component are state hooks.
With that being the goal, could I get some help on the implementation?
Here's what a component call looks like in the parent:
const [allBlueItems, setAllBlueItems] = useState([]);
const [selectedBlueItems, setSelectedBlueItems] = useState([]);
const [allRedItems, setAllRedItems] = useState([]);
const [selectedRedItems, setSelectedRedItems] = useState([]);
<BlueSelection
allBlueItems={allBlueItems}
selectedBlueItems={setSelectedBlueItems}
setSelectedBlueItems={selectedBlueItems}
/>
<RedSelection
allRedItems={allRedItems}
selectedRedItems={setSelectedRedItems}
setSelectedRedItems={selectedRedItems}
/>
Then, the ItemSelection component uses those useState values passed in as props to render data, and updates the state accordingly:
const BlueSelection = ({ allBlueItems, selectedBlueItems, setSelectedBlueItems }) => {
useEffect(() => {
setSelectedBlueItems([]);
}
}, []);
Then I repeat myself and implement the exact same code to handle the RedItem
const RedSelection = ({ allRedItems, selectedRedItems, setSelectedRedItems }) => {
useEffect(() => {
setSelectedRedItems([]);
}
}, []);
Refactor
export default const Selection = () => {
const [allItems, setAllItems] = useState([]);
const [selectedItems, setSelectedItems] = useState([]);
return (
<Selection
allItems={allItems}
selectedItems={setSelectedItems}
setSelectedItems={selectedItems}
/>)}
import this wherever you need it like....
import Selection as RedSelection from ...
import Selection as BlueSelection from...
I have component
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
const data = {
"1": ["1a", "1b"],
"2": ["2a"]
};
const slugs = {
"1a": { text: "Lorem" },
"1b": { text: "ipsum" },
"2a": { text: "..." }
};
const ExamplePage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [index, setIndex] = useState(0);
useEffect(() => {
setIndex(0);
}, [id]);
console.log("state", {
id,
index
});
const slug = data[id][index];
const text = slugs[slug].text;
function onPrev(): void {
if (index <= 0) {
return;
}
setIndex((index) => index - 1);
}
function onNext(): void {
if (index >= data[id].length - 1) {
return;
}
setIndex((index) => index + 1);
}
return (
<div>
<button onClick={onPrev}>Prev</button>
<span>{text}</span>
<button onClick={onNext}>Next</button>
</div>
);
};
export default ExamplePage;
And Route for this:
<Route path="/:id" component={ExamplePage} />
Live version: https://codesandbox.io/s/react-hooks-ewl4d
There is a bug with this code when:
User is on /1 url
User clicks button "Next"
User clicks link to /2 url
In this case id will be "2", index will be 1, but there isn't data["2"][1].
As you can see useEffect don't help in this case because useEffect don't stop current function call.
My question is: How I can ensure that this state will be always correct?
I know that I can write const text = slugs[slug]?.text; and this solve my bug, but still, in one moment, component have incorrect state. I wondering if is a way to prevent this incorrect state.
In React class component this problem can be solved by getDerivedStateFromProps - You can see this live on https://codesandbox.io/s/react-hooks-solve-in-react-component-xo43g
The useEffect will run async so you are trying to set the slug and text before the index has updated.
You can put the slug and text into state and then use another useEffect to update them when the index or id changes:
const { id } = useParams();
const [index, setIndex] = useState(0);
const [slug, setSlug] = useState();
const [text, setText] = useState();
useEffect(() => {
setIndex(0);
}, [id]);
useEffect(() => {
const newSlug = data[id][index];
if (!newSlug) return; // If id changes but index has not updated yet
setSlug(newSlug);
setText(slugs[newSlug].text);
}, [id, index]);
import React, { useState, useEffect } from "react";
export default function App() {
const [columns, setColumns] = useState([
{ name: "a" },
{ name: "b" },
{ name: "c" }
]);
const [isOpen, setIsOpen] = useState(false);
const addName = () => setColumns([...columns, { name: "r" }]);
const toggleOpen = () => setIsOpen(!isOpen);
return (
<>
<List columns={columns} />
<button onClick={toggleOpen}>Toggle</button>
<button onClick={addName}>Add</button>
<p>{isOpen.toString()}</p>
</>
);
}
const List = ({ columns }) => {
const names = columns.map(col => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
Names is changed to: is called, when isOpen state is changed in App component.
I want the console.log to be executed only when names array is changed.
I think in List component, it is creating a new array whenever render, so that the previous array and the new array are not equal.
You should memoize the component so it will render only on props change (or if comparison function passed as 2nd argument).
Currently, List rendered due to its parent App render.
const List = ({ columns }) => {
const names = columns.map((col) => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
const MemoList = React.memo(List);
export default function App() {
return (
<>
<MemoList columns={columns} />
</>
);
}
See working example:
For class component, use React.PureComponent or implement shouldComponentUpdate.
const names = columns.map(col => col.name);
Creates a new array every time and useEffect thinks that dependencies have changed.
To avoid that either pass names directly to useEffect:
useEffect(() => {
console.log("Names is changed to: ", names);
}, names);
Or useMemo to get the same array object:
const names = useMemo(() => columns.map(
col => col.name
), [columns]);
I recently started using react hooks a lot.
State management seems more intuitive to me when using "React.useState".
Anyway, while it's working ok, I know it's starting to look cluttered the more values I am starting to save to state.
For example, as my car parts app has progressed, it is now looking like this:
const [isShown, setIsShown] = React.useState(false);
const [idVal, setIdValue] = React.useState(false);
const [partNameVal, setPartNameValue] = React.useState(false);
const [engineEngineEngineTypeVal, setEngineEngineTypeValue] = React.useState(false);
const [displacementVal, setDisplacementValue] = React.useState(false);
const [makeVal, setMakeValue] = React.useState(false);
const [countryVal, setCountryValue] = React.useState(false);
const hide = () => setIsShown(false);
const show = (id, partName, engineEngineType, displacement, department, country) => {
setIsShown(true);
setIdValue(id);
setPartNameValue(partName);
setEngineTypeValue(engineEngineType);
setDisplacementValue(displacement);
setMakeValue(department);
setCountryValue(country);
}
<p>ID: {idVal}</p>
<p>PartName: {partNameVal}</p>
<p>EngineType: {engineEngineTypeVal}</p>
<p>Displacement: {displacementVal}</p>
<p>Make: {makeVal}</p>
<p>Country: {countryVal}</p>
I was wondering if there's a way to make this more readable, but still be very intuitive.
Thanks!
Typically you want to handle a single object or use useReducer, something like:
const INITIAL_CAR = {
id: 0,
part: "4xE3",
country: "USA",
// ... More entries
};
const CarApp = () => {
const [car, setCar] = useState(INITIAL_CAR);
const [isShown, setIsShown] = useState(false);
const show = (carProps) => {
setIsShown(true);
setCar(carProps);
};
const { id, part, engine, displacement, make, county } = car;
const updateCountry = (country) =>
setCar((prevCar) => ({ ...prevCar, country }));
const updateCarProperty = ({ property, value }) =>
setCar((prevCar) => ({ ...prevCar, [property]: value }));
return (
<div>
{isShown && (
<>
<p>ID: {id}</p>
<p>PartName: {part}</p>
<p>EngineType: {engine}</p>
<p>Displacement: {displacement}</p>
<p>Make: {make}</p>
<p>Country: {country}</p>{" "}
</>
)}
// use show, updateCountry, updateProperty etc.
</div>
);
};
I'd say that it's the case for useReducer hook.
https://reactjs.org/docs/hooks-reference.html#usereducer
const initialState = {
isShown: false,
idVal: 0,
....
};
function reducer(state, action) {
switch (action.type) {
case 'show':
return {
...state,
isShown: true,
idVal: action.payload.idVal
};
case 'hide':
return {
...state,
isShown: false
}
...
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({type: 'show', payload: { idVal: 1}})
The way I mostly handle this much of state in a component is using one useState, that way it's just a big object.
Here is a small example :
const [state, setState] = useState({
num: 1,
cars: ['volvo', 'mazda'],
john: {name: 'John', last: 'Foo'}
})
And if you want to change something in that I usually use this function
const onChange = (name, value) => {
setState(prevState => ({...prevState, [name]: value}))
}
This will change the key name to the value value. This is way clearer in my eyes.
You can make a new file to extract all your hook logic from your component.
Call if for example useHooks.js
export default () => {
const [isShown, setIsShown] = React.useState(false);
const [idVal, setIdValue] = React.useState(false);
const [partNameVal, setPartNameValue] = React.useState(false);
const [engineEngineEngineTypeVal, setEngineEngineTypeValue] = React.useState(false);
const [displacementVal, setDisplacementValue] = React.useState(false);
const [makeVal, setMakeValue] = React.useState(false);
const [countryVal, setCountryValue] = React.useState(false);
const hide = () => setIsShown(false);
const show = (id, partName, engineEngineType, displacement, department, country) => {
setIsShown(true);
setIdValue(id);
setPartNameValue(partName);
setEngineTypeValue(engineEngineType);
setDisplacementValue(displacement);
setMakeValue(department);
setCountryValue(country);
}
return [isShown, idVal, partNameVal, engineEngineEngineTypeVal, displacementVal,
makeVal, countryVal, show, hide];
}
The idea was here to put all your hooks logic in a function and return values that you need inside your JSX.
And in your component import and use all properties exported from useHooks
import useHooks from './useHooks';
const [isShown, idVal, partNameVal, engineEngineEngineTypeVal, displacementVal,
makeVal, countryVal, show, hide] = useHooks();
Hope the idea is clear